Spike PHPCoverage Details: outlook8.php

Line #FrequencySource Line
1 <?php
2 /*********************************************************************************/
3 /**
4  * iCalcreator v2.16.12
5  * copyright (c) 2007-2013 Kjell-Inge Gustafsson kigkonsult
6  * kigkonsult.se/iCalcreator/index.php
7  * ical@kigkonsult.se
8  *
9  * Description:
10  * This file is a PHP implementation of rfc2445/rfc5545.
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2.1 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25  */
26 /*********************************************************************************/
27 /*********************************************************************************/
28 /*         A little setup                                                        */
29 /*********************************************************************************/
30             /* your local language code */
31 // define( 'ICAL_LANG', 'sv' );
32             // alt. autosetting
33 /*
34 $langstr     = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
35 $pos         = strpos( $langstr, ';' );
36 if ($pos   !== false) {
37   $langstr   = substr( $langstr, 0, $pos );
38   $pos       = strpos( $langstr, ',' );
39   if ($pos !== false) {
40     $pos     = strpos( $langstr, ',' );
41     $langstr = substr( $langstr, 0, $pos );
42   }
43   define( 'ICAL_LANG', $langstr );
44 }
45 */
46 /*********************************************************************************/
47 /*         version, do NOT remove!!                                              */
48 define( 'ICALCREATOR_VERSION', 'iCalcreator 2.16.12' );
49 /*********************************************************************************/
50 /*********************************************************************************/
51 /**
52  * vcalendar class
53  *
54  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
55  * @since 2.9.6 - 2011-05-14
56  */
57 class vcalendar {
58             //  calendar property variables
59   var $calscale;
60   var $method;
61   var $prodid;
62   var $version;
63   var $xprop;
64             //  container for calendar components
65   var $components;
66             //  component config variables
67   var $allowEmpty;
68   var $unique_id;
69   var $language;
70   var $directory;
71   var $filename;
72   var $url;
73   var $delimiter;
74   var $nl;
75   var $format;
76   var $dtzid;
77             //  component internal variables
78   var $attributeDelimiter;
79   var $valueInit;
80             //  component xCal declaration container
81   var $xcaldecl;
82 /**
83  * constructor for calendar object
84  *
85  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
86  * @since 2.9.6 - 2011-05-14
87  * @param array $config
88  * @return void
89  */
90   function vcalendar ( $config = array()) {
91     $this->_makeVersion();
92     $this->calscale   = null;
93     $this->method     = null;
94     $this->_makeUnique_id();
95     $this->prodid     = null;
96     $this->xprop      = array();
97     $this->language   = null;
98     $this->directory  = null;
99     $this->filename   = null;
100     $this->url        = null;
101     $this->dtzid      = null;
102 /**
103  *   language = <Text identifying a language, as defined in [RFC 1766]>
104  */
105     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
106                                           $config['language']   = ICAL_LANG;
107     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
108     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
109     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
110     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
111     $this->setConfig( $config );
112 
113     $this->xcaldecl   = array();
114     $this->components = array();
115   }
116 /*********************************************************************************/
117 /**
118  * Property Name: CALSCALE
119  */
120 /**
121  * creates formatted output for calendar property calscale
122  *
123  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
124  * @since 2.10.16 - 2011-10-28
125  * @return string
126  */
127   function createCalscale() {
128     if( empty( $this->calscale )) return FALSE;
129     switch( $this->format ) {
130       case 'xcal':
131         return $this->nl.' calscale="'.$this->calscale.'"';
132         break;
133       default:
134         return 'CALSCALE:'.$this->calscale.$this->nl;
135         break;
136     }
137   }
138 /**
139  * set calendar property calscale
140  *
141  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
142  * @since 2.4.8 - 2008-10-21
143  * @param string $value
144  * @return void
145  */
146   function setCalscale( $value ) {
147     if( empty( $value )) return FALSE;
148     $this->calscale = $value;
149   }
150 /*********************************************************************************/
151 /**
152  * Property Name: METHOD
153  */
154 /**
155  * creates formatted output for calendar property method
156  *
157  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
158  * @since 2.10.16 - 2011-10-28
159  * @return string
160  */
161   function createMethod() {
162     if( empty( $this->method )) return FALSE;
163     switch( $this->format ) {
164       case 'xcal':
165         return $this->nl.' method="'.$this->method.'"';
166         break;
167       default:
168         return 'METHOD:'.$this->method.$this->nl;
169         break;
170     }
171   }
172 /**
173  * set calendar property method
174  *
175  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
176  * @since 2.4.8 - 2008-20-23
177  * @param string $value
178  * @return bool
179  */
180   function setMethod( $value ) {
181     if( empty( $value )) return FALSE;
182     $this->method = $value;
183     return TRUE;
184   }
185 /*********************************************************************************/
186 /**
187  * Property Name: PRODID
188  *
189  *  The identifier is RECOMMENDED to be the identical syntax to the
190  * [RFC 822] addr-spec. A good method to assure uniqueness is to put the
191  * domain name or a domain literal IP address of the host on which.. .
192  */
193 /**
194  * creates formatted output for calendar property prodid
195  *
196  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
197  * @since 2.12.11 - 2012-05-13
198  * @return string
199  */
200   function createProdid() {
201     if( !isset( $this->prodid ))
202       $this->_makeProdid();
203     switch( $this->format ) {
204       case 'xcal':
205         return $this->nl.' prodid="'.$this->prodid.'"';
206         break;
207       default:
208         $toolbox = new calendarComponent();
209         $toolbox->setConfig( $this->getConfig());
210         return $toolbox->_createElement( 'PRODID', '', $this->prodid );
211         break;
212     }
213   }
214 /**
215  * make default value for calendar prodid
216  *
217  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
218  * @since 2.6.8 - 2009-12-30
219  * @return void
220  */
221   function _makeProdid() {
222     $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
223   }
224 /**
225  * Conformance: The property MUST be specified once in an iCalendar object.
226  * Description: The vendor of the implementation SHOULD assure that this
227  * is a globally unique identifier; using some technique such as an FPI
228  * value, as defined in [ISO 9070].
229  */
230 /**
231  * make default unique_id for calendar prodid
232  *
233  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
234  * @since 0.3.0 - 2006-08-10
235  * @return void
236  */
237   function _makeUnique_id() {
238     $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
239   }
240 /*********************************************************************************/
241 /**
242  * Property Name: VERSION
243  *
244  * Description: A value of "2.0" corresponds to this memo.
245  */
246 /**
247  * creates formatted output for calendar property version
248 
249  *
250  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
251  * @since 2.10.16 - 2011-10-28
252  * @return string
253  */
254   function createVersion() {
255     if( empty( $this->version ))
256       $this->_makeVersion();
257     switch( $this->format ) {
258       case 'xcal':
259         return $this->nl.' version="'.$this->version.'"';
260         break;
261       default:
262         return 'VERSION:'.$this->version.$this->nl;
263         break;
264     }
265   }
266 /**
267  * set default calendar version
268  *
269  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
270  * @since 0.3.0 - 2006-08-10
271  * @return void
272  */
273   function _makeVersion() {
274     $this->version = '2.0';
275   }
276 /**
277  * set calendar version
278  *
279  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
280  * @since 2.4.8 - 2008-10-23
281  * @param string $value
282  * @return void
283  */
284   function setVersion( $value ) {
285     if( empty( $value )) return FALSE;
286     $this->version = $value;
287     return TRUE;
288   }
289 /*********************************************************************************/
290 /**
291  * Property Name: x-prop
292  */
293 /**
294  * creates formatted output for calendar property x-prop, iCal format only
295  *
296  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
297  * @since 2.16.2 - 2012-12-18
298  * @return string
299  */
300   function createXprop() {
301     if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
302     $output = null;
303     $toolbox = new calendarComponent();
304     $toolbox->setConfig( $this->getConfig());
305     foreach( $this->xprop as $label => $xpropPart ) {
306       if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
307         $output  .= $toolbox->_createElement( $label );
308         continue;
309       }
310       $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
311       if( is_array( $xpropPart['value'] )) {
312         foreach( $xpropPart['value'] as $pix => $theXpart )
313           $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->nl );
314         $xpropPart['value']  = implode( ',', $xpropPart['value'] );
315       }
316       else
317         $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
318       $output    .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
319       if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
320         foreach( $toolbox->xcaldecl as $localxcaldecl )
321           $this->xcaldecl[] = $localxcaldecl;
322       }
323     }
324     return $output;
325   }
326 /**
327  * set calendar property x-prop
328  *
329  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
330  * @since 2.11.9 - 2012-01-16
331  * @param string $label
332  * @param string $value
333  * @param array $params optional
334  * @return bool
335  */
336   function setXprop( $label, $value, $params=FALSE ) {
337     if( empty( $label ))
338       return FALSE;
339     if( 'X-' != strtoupper( substr( $label, 0, 2 )))
340       return FALSE;
341     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
342     $xprop           = array( 'value' => $value );
343     $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
344     if( !is_array( $this->xprop )) $this->xprop = array();
345     $this->xprop[strtoupper( $label )] = $xprop;
346     return TRUE;
347   }
348 /*********************************************************************************/
349 /**
350  * delete calendar property value
351  *
352  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
353  * @since 2.8.8 - 2011-03-15
354  * @param mixed $propName, bool FALSE => X-property
355  * @param int $propix, optional, if specific property is wanted in case of multiply occurences
356  * @return bool, if successfull delete
357  */
358   function deleteProperty( $propName=FALSE, $propix=FALSE ) {
359     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
360     if( !$propix )
361       $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
362     $this->propdelix[$propName] = --$propix;
363     $return = FALSE;
364     switch( $propName ) {
365       case 'CALSCALE':
366         if( isset( $this->calscale )) {
367           $this->calscale = null;
368           $return = TRUE;
369         }
370         break;
371       case 'METHOD':
372         if( isset( $this->method )) {
373           $this->method   = null;
374           $return = TRUE;
375         }
376         break;
377       default:
378         $reduced = array();
379         if( $propName != 'X-PROP' ) {
380           if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
381           foreach( $this->xprop as $k => $a ) {
382             if(( $k != $propName ) && !empty( $a ))
383               $reduced[$k] = $a;
384           }
385         }
386         else {
387           if( count( $this->xprop ) <= $propix )  return FALSE;
388           $xpropno = 0;
389           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
390             if( $propix != $xpropno )
391               $reduced[$xpropkey] = $xpropvalue;
392             $xpropno++;
393           }
394         }
395         $this->xprop = $reduced;
396         if( empty( $this->xprop )) {
397           unset( $this->propdelix[$propName] );
398           return FALSE;
399         }
400         return TRUE;
401     }
402     return $return;
403   }
404 /**
405  * get calendar property value/params
406  *
407  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
408  * @since 2.13.4 - 2012-08-08
409  * @param string $propName, optional
410  * @param int $propix, optional, if specific property is wanted in case of multiply occurences
411  * @param bool $inclParam=FALSE
412  * @return mixed
413  */
414   function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
415     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
416     if( 'X-PROP' == $propName ) {
417       if( !$propix )
418         $propix  = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
419       $this->propix[$propName] = --$propix;
420     }
421     else
422       $mProps    = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
423     switch( $propName ) {
424       case 'ATTENDEE':
425       case 'CATEGORIES':
426       case 'CONTACT':
427       case 'DTSTART':
428       case 'GEOLOCATION':
429       case 'LOCATION':
430       case 'ORGANIZER':
431       case 'PRIORITY':
432       case 'RESOURCES':
433       case 'STATUS':
434       case 'SUMMARY':
435       case 'RECURRENCE-ID-UID':
436       case 'RELATED-TO':
437       case 'R-UID':
438       case 'UID':
439       case 'URL':
440         $output  = array();
441         foreach ( $this->components as $cix => $component) {
442           if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
443             continue;
444           if( in_array( strtoupper( $propName ), $mProps )) {
445             $component->_getProperties( $propName, $output );
446             continue;
447           }
448           elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
449             if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
450               $content = $component->getProperty( 'UID' );
451           }
452           elseif( 'GEOLOCATION' == $propName ) {
453             $content = $component->getProperty( 'LOCATION' );
454             $content = ( !empty( $content )) ? $content.' ' : '';
455             if(( FALSE === ( $geo     = $component->getProperty( 'GEO' ))) || empty( $geo ))
456               continue;
457             if( 0.0 < $geo['latitude'] )
458               $sign   = '+';
459             else
460               $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
461             $content .= ' '.$sign.sprintf( "%09.6f", abs( $geo['latitude'] ));
462             $content  = rtrim( rtrim( $content, '0' ), '.' );
463             if( 0.0 < $geo['longitude'] )
464               $sign   = '+';
465             else
466               $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
467             $content .= $sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';
468           }
469           elseif( FALSE === ( $content = $component->getProperty( $propName )))
470             continue;
471           if(( FALSE === $content ) || empty( $content ))
472             continue;
473           elseif( is_array( $content )) {
474             if( isset( $content['year'] )) {
475               $key  = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
476               if( !isset( $output[$key] ))
477                 $output[$key] = 1;
478               else
479                 $output[$key] += 1;
480             }
481             else {
482               foreach( $content as $partValue => $partCount ) {
483                 if( !isset( $output[$partValue] ))
484                   $output[$partValue] = $partCount;
485                 else
486                   $output[$partValue] += $partCount;
487               }
488             }
489           } // end elseif( is_array( $content )) {
490           elseif( !isset( $output[$content] ))
491             $output[$content] = 1;
492           else
493             $output[$content] += 1;
494         } // end foreach ( $this->components as $cix => $component)
495         if( !empty( $output ))
496           ksort( $output );
497         return $output;
498         break;
499       case 'CALSCALE':
500         return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
501         break;
502       case 'METHOD':
503         return ( !empty( $this->method )) ? $this->method : FALSE;
504         break;
505       case 'PRODID':
506         if( empty( $this->prodid ))
507           $this->_makeProdid();
508         return $this->prodid;
509         break;
510       case 'VERSION':
511         return ( !empty( $this->version )) ? $this->version : FALSE;
512         break;
513       default:
514         if( $propName != 'X-PROP' ) {
515           if( !isset( $this->xprop[$propName] )) return FALSE;
516           return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
517                                 : array( $propName, $this->xprop[$propName]['value'] );
518         }
519         else {
520           if( empty( $this->xprop )) return FALSE;
521           $xpropno = 0;
522           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
523             if( $propix == $xpropno )
524               return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
525                                     : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
526             else
527               $xpropno++;
528           }
529           unset( $this->propix[$propName] );
530           return FALSE; // not found ??
531         }
532     }
533     return FALSE;
534   }
535 /**
536  * general vcalendar property setting
537  *
538  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
539  * @since 2.2.13 - 2007-11-04
540  * @param mixed $args variable number of function arguments,
541  *                    first argument is ALWAYS component name,
542  *                    second ALWAYS component value!
543  * @return bool
544  */
545   function setProperty () {
546     $numargs    = func_num_args();
547     if( 1 > $numargs )
548       return FALSE;
549     $arglist    = func_get_args();
550     $arglist[0] = strtoupper( $arglist[0] );
551     switch( $arglist[0] ) {
552       case 'CALSCALE':
553         return $this->setCalscale( $arglist[1] );
554       case 'METHOD':
555         return $this->setMethod( $arglist[1] );
556       case 'VERSION':
557         return $this->setVersion( $arglist[1] );
558       default:
559         if( !isset( $arglist[1] )) $arglist[1] = null;
560         if( !isset( $arglist[2] )) $arglist[2] = null;
561         return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
562     }
563     return FALSE;
564   }
565 /*********************************************************************************/
566 /**
567  * get vcalendar config values or * calendar components
568  *
569  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
570  * @since 2.11.7 - 2012-01-12
571  * @param mixed $config
572  * @return value
573  */
574   function getConfig( $config = FALSE ) {
575     if( !$config ) {
576       $return = array();
577       $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
578       $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
579       $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
580       $return['FILENAME']    = $this->getConfig( 'FILENAME' );
581       $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
582       $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
583       $return['FORMAT']      = $this->getConfig( 'FORMAT' );
584       if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
585         $return['LANGUAGE']  = $lang;
586       $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
587       $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
588       if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
589         $return['URL']       = $url;
590       $return['TZID']        = $this->getConfig( 'TZID' );
591       return $return;
592     }
593     switch( strtoupper( $config )) {
594       case 'ALLOWEMPTY':
595         return $this->allowEmpty;
596         break;
597       case 'COMPSINFO':
598         unset( $this->compix );
599         $info = array();
600         foreach( $this->components as $cix => $component ) {
601           if( empty( $component )) continue;
602           $info[$cix]['ordno'] = $cix + 1;
603           $info[$cix]['type']  = $component->objName;
604           $info[$cix]['uid']   = $component->getProperty( 'uid' );
605           $info[$cix]['props'] = $component->getConfig( 'propinfo' );
606           $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
607         }
608         return $info;
609         break;
610       case 'DELIMITER':
611         return $this->delimiter;
612         break;
613       case 'DIRECTORY':
614         if( empty( $this->directory ) && ( '0' != $this->directory ))
615           $this->directory = '.';
616         return $this->directory;
617         break;
618       case 'DIRFILE':
619         return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
620         break;
621       case 'FILEINFO':
622         return array( $this->getConfig( 'directory' )
+623                     , $this->getConfig( 'filename' )
+624                     , $this->getConfig( 'filesize' ));
625         break;
626       case 'FILENAME':
627         if( empty( $this->filename ) && ( '0' != $this->filename )) {
628           if( 'xcal' == $this->format )
629             $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
630           else
631             $this->filename = date( 'YmdHis' ).'.ics';
632         }
633         return $this->filename;
634         break;
635       case 'FILESIZE':
636         $size    = 0;
637         if( empty( $this->url )) {
638           $dirfile = $this->getConfig( 'dirfile' );
639           if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
640             $size = 0;
641           clearstatcache();
642         }
643         return $size;
644         break;
645       case 'FORMAT':
646         return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
647         break;
648       case 'LANGUAGE':
649          /* get language for calendar component as defined in [RFC 1766] */
650         return $this->language;
651         break;
652       case 'NL':
653       case 'NEWLINECHAR':
654         return $this->nl;
655         break;
656       case 'TZID':
657         return $this->dtzid;
658         break;
659       case 'UNIQUE_ID':
660         return $this->unique_id;
661         break;
662       case 'URL':
663         if( !empty( $this->url ))
664           return $this->url;
665         else
666           return FALSE;
667         break;
668     }
669   }
670 /**
671  * general vcalendar config setting
672  *
673  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
674  * @since 2.16.7 - 2013-01-11
675  * @param mixed  $config
676  * @param string $value
677  * @return void
678  */
679   function setConfig( $config, $value = FALSE) {
680     if( is_array( $config )) {
681       $ak = array_keys( $config );
682       foreach( $ak as $k ) {
683         if( 'DIRECTORY' == strtoupper( $k )) {
684           if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
685             return FALSE;
686           unset( $config[$k] );
687         }
688         elseif( 'NEWLINECHAR' == strtoupper( $k )) {
689           if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
690             return FALSE;
691           unset( $config[$k] );
692         }
693       }
694       foreach( $config as $cKey => $cValue ) {
695         if( FALSE === $this->setConfig( $cKey, $cValue ))
696           return FALSE;
697       }
698       return TRUE;
699     }
700     $res = FALSE;
701     switch( strtoupper( $config )) {
702       case 'ALLOWEMPTY':
703         $this->allowEmpty = $value;
704         $subcfg  = array( 'ALLOWEMPTY' => $value );
705         $res = TRUE;
706         break;
707       case 'DELIMITER':
708         $this->delimiter = $value;
709         return TRUE;
710         break;
711       case 'DIRECTORY':
712         $value   = trim( $value );
713         $del     = $this->getConfig('delimiter');
714         if( $del == substr( $value, ( 0 - strlen( $del ))))
715           $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
716         if( is_dir( $value )) {
717             /* local directory */
718           clearstatcache();
719           $this->directory = $value;
720           $this->url       = null;
721           return TRUE;
722         }
723         else
724           return FALSE;
725         break;
726       case 'FILENAME':
727         $value   = trim( $value );
728         if( !empty( $this->url )) {
729             /* remote directory+file -> URL */
730           $this->filename = $value;
731           return TRUE;
732         }
733         $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
734         if( file_exists( $dirfile )) {
735             /* local file exists */
736           if( is_readable( $dirfile ) || is_writable( $dirfile )) {
737             clearstatcache();
738             $this->filename = $value;
739             return TRUE;
740           }
741           else
742             return FALSE;
743         }
744         elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
745             /* read- or writable directory */
746           $this->filename = $value;
747           return TRUE;
748         }
749         else
750           return FALSE;
751         break;
752       case 'FORMAT':
753         $value   = trim( strtolower( $value ));
754         if( 'xcal' == $value ) {
755           $this->format             = 'xcal';
756           $this->attributeDelimiter = $this->nl;
757           $this->valueInit          = null;
758         }
759         else {
760           $this->format             = null;
761           $this->attributeDelimiter = ';';
762           $this->valueInit          = ':';
763         }
764         $subcfg  = array( 'FORMAT' => $value );
765         $res = TRUE;
766         break;
767       case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766]
768         $value   = trim( $value );
769         $this->language = $value;
770         $this->_makeProdid();
771         $subcfg  = array( 'LANGUAGE' => $value );
772         $res = TRUE;
773         break;
774       case 'NL':
775       case 'NEWLINECHAR':
776         $this->nl = $value;
777         if( 'xcal' == $value ) {
778           $this->attributeDelimiter = $this->nl;
779           $this->valueInit          = null;
780         }
781         else {
782           $this->attributeDelimiter = ';';
783           $this->valueInit          = ':';
784         }
785         $subcfg  = array( 'NL' => $value );
786         $res = TRUE;
787         break;
788       case 'TZID':
789         $this->dtzid = $value;
790         $subcfg  = array( 'TZID' => $value );
791         $res = TRUE;
792         break;
793       case 'UNIQUE_ID':
794         $value   = trim( $value );
795         $this->unique_id = $value;
796         $this->_makeProdid();
797         $subcfg  = array( 'UNIQUE_ID' => $value );
798         $res = TRUE;
799         break;
800       case 'URL':
801             /* remote file - URL */
802         $value     = str_replace( array( 'HTTP://', 'WEBCAL://', 'webcal://' ), 'http://', trim( $value ));
803         if( 'http://' != substr( $value, 0, 7 ))
804           return FALSE;
805         $s1        = $this->url;
806         $this->url = $value;
807         $s2        = $this->directory;
808         $this->directory = null;
809         $parts     = pathinfo( $value );
810         if( FALSE === $this->setConfig( 'filename',  $parts['basename'] )) {
811           $this->url       = $s1;
812           $this->directory = $s2;
813           return FALSE;
814         }
815         else
816           return TRUE;
817         break;
818       default:  // any unvalid config key.. .
819         return TRUE;
820     }
821     if( !$res ) return FALSE;
822     if( isset( $subcfg ) && !empty( $this->components )) {
823       foreach( $subcfg as $cfgkey => $cfgvalue ) {
824         foreach( $this->components as $cix => $component ) {
825           $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
826           if( !$res )
827             break 2;
828           $this->components[$cix] = $component->copy(); // PHP4 compliant
829         }
830       }
831     }
832     return $res;
833   }
834 /*********************************************************************************/
835 /**
836  * add calendar component to container
837  *
838  * alias to setComponent
839  *
840  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
841  * @since 1.x.x - 2007-04-24
842  * @param object $component calendar component
843  * @return void
844  */
845   function addComponent( $component ) {
846     $this->setComponent( $component );
847   }
848 /**
849  * delete calendar component from container
850  *
851  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
852  * @since 2.8.8 - 2011-03-15
853  * @param mixed $arg1 ordno / component type / component uid
854  * @param mixed $arg2 optional, ordno if arg1 = component type
855  * @return void
856  */
857   function deleteComponent( $arg1, $arg2=FALSE  ) {
858     $argType = $index = null;
859     if ( ctype_digit( (string) $arg1 )) {
860       $argType = 'INDEX';
861       $index   = (int) $arg1 - 1;
862     }
863     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
864       $argType = strtolower( $arg1 );
865       $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
866     }
867     $cix1dC = 0;
868     foreach ( $this->components as $cix => $component) {
869       if( empty( $component )) continue;
870       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
871         unset( $this->components[$cix] );
872         return TRUE;
873       }
874       elseif( $argType == $component->objName ) {
875         if( $index == $cix1dC ) {
876           unset( $this->components[$cix] );
877           return TRUE;
878         }
879         $cix1dC++;
880       }
881       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
882         unset( $this->components[$cix] );
883         return TRUE;
884       }
885     }
886     return FALSE;
887   }
888 /**
889  * get calendar component from container
890  *
891  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
892  * @since 2.13.5 - 2012-08-08
893  * @param mixed $arg1 optional, ordno/component type/ component uid
894  * @param mixed $arg2 optional, ordno if arg1 = component type
895  * @return object
896  */
897   function getComponent( $arg1=FALSE, $arg2=FALSE ) {
898     $index = $argType = null;
899     if ( !$arg1 ) { // first or next in component chain
900       $argType = 'INDEX';
901       $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
902     }
903     elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
904       $argType = 'INDEX';
905       $index   = (int) $arg1;
906       unset( $this->compix );
907     }
908     elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
909       $arg2  = implode( '-', array_keys( $arg1 ));
910       $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
911       $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
912       $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
913       $mProps     = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
914     }
915     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
916       unset( $this->compix['INDEX'] );
917       $argType = strtolower( $arg1 );
918       if( !$arg2 )
919         $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
920       elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
921         $index = (int) $arg2;
922     }
923     elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
924       if( !$arg2 )
925         $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
926       elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
927         $index = (int) $arg2;
928     }
929     if( isset( $index ))
930       $index  -= 1;
931     $ckeys = array_keys( $this->components );
932     if( !empty( $index) && ( $index > end(  $ckeys )))
933       return FALSE;
934     $cix1gC = 0;
935     foreach ( $this->components as $cix => $component) {
936       if( empty( $component )) continue;
937       if(( 'INDEX' == $argType ) && ( $index == $cix ))
938         return $component->copy();
939       elseif( $argType == $component->objName ) {
940         if( $index == $cix1gC )
941           return $component->copy();
942         $cix1gC++;
943       }
944       elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
945         $hit = array();
946         foreach( $arg1 as $pName => $pValue ) {
947           $pName = strtoupper( $pName );
948           if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
949             continue;
950           if( in_array( $pName, $mProps )) { // multiple occurrence
951             $propValues = array();
952             $component->_getProperties( $pName, $propValues );
953             $propValues = array_keys( $propValues );
954             $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
955             continue;
956           } // end   if(.. .// multiple occurrence
957           if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence
958             $hit[] = FALSE; // missing property
959             continue;
960           }
961           if( 'SUMMARY' == $pName ) { // exists within (any case)
962             $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE;
963             continue;
964           }
965           if( in_array( strtoupper( $pName ), $dateProps )) {
966             $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
967             if( 8 < strlen( $pValue )) {
968               if( isset( $value['hour'] )) {
969                 if( 'T' == substr( $pValue, 8, 1 ))
970                   $pValue = str_replace( 'T', '', $pValue );
971                 $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
972               }
973               else
974                 $pValue = substr( $pValue, 0, 8 );
975             }
976             $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE;
977             continue;
978           }
979           elseif( !is_array( $value ))
980             $value = array( $value );
981           foreach( $value as $part ) {
982             $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
983             foreach( $part as $subPart ) {
984               if( $pValue == $subPart ) {
985                 $hit[] = TRUE;
986                 continue 3;
987               }
988             }
989           } // end foreach( $value as $part )
990           $hit[] = FALSE; // no hit in property
991         } // end  foreach( $arg1 as $pName => $pValue )
992         if( in_array( TRUE, $hit )) {
993           if( $index == $cix1gC )
994             return $component->copy();
995           $cix1gC++;
996         }
997       } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
998       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
999         if( $index == $cix1gC )
1000           return $component->copy();
1001         $cix1gC++;
1002       }
1003     } // end foreach ( $this->components.. .
1004             /* not found.. . */
1005     unset( $this->compix );
1006     return FALSE;
1007   }
1008 /**
1009  * create new calendar component, already included within calendar
1010  *
1011  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1012  * @since 2.6.33 - 2011-01-03
1013  * @param string $compType component type
1014  * @return object (reference)
1015  */
1016   function & newComponent( $compType ) {
1017     $config = $this->getConfig();
1018     $keys   = array_keys( $this->components );
1019     $ix     = end( $keys) + 1;
1020     switch( strtoupper( $compType )) {
1021       case 'EVENT':
1022       case 'VEVENT':
1023         $this->components[$ix] = new vevent( $config );
1024         break;
1025       case 'TODO':
1026       case 'VTODO':
1027         $this->components[$ix] = new vtodo( $config );
1028         break;
1029       case 'JOURNAL':
1030       case 'VJOURNAL':
1031         $this->components[$ix] = new vjournal( $config );
1032         break;
1033       case 'FREEBUSY':
1034       case 'VFREEBUSY':
1035         $this->components[$ix] = new vfreebusy( $config );
1036         break;
1037       case 'TIMEZONE':
1038       case 'VTIMEZONE':
1039         array_unshift( $this->components, new vtimezone( $config ));
1040         $ix = 0;
1041         break;
1042       default:
1043         return FALSE;
1044     }
1045     return $this->components[$ix];
1046   }
1047 /**
1048  * select components from calendar on date or selectOption basis
1049  *
1050  * Ensure DTSTART is set for every component.
1051  * No date controls occurs.
1052  *
1053  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1054  * @since 2.16.12 - 2013-02-10
1055  * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
1056  * @param int   $startM optional, start Month, default current Month
1057  * @param int   $startD optional, start Day,   default current Day
1058  * @param int   $endY   optional, end   Year,  default $startY
1059  * @param int   $endY   optional, end   Month, default $startM
1060  * @param int   $endY   optional, end   Day,   default $startD
1061  * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
1062  * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
1063  *                                TRUE            => output : array[] (ignores split)
1064  * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
1065  *                                FALSE          - only component(-s) that starts within period
1066  * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
1067  *                                                 period (implies flat=FALSE)
1068  *                                FALSE          - one occurance of component only in output array
1069  * @return array or FALSE
1070  */
1071   function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
1072             /* check  if empty calendar */
1073     if( 0 >= count( $this->components )) return FALSE;
1074     if( is_array( $startY ))
1075       return $this->selectComponents2( $startY );
1076             /* check default dates */
1077     if( !$startY ) $startY = date( 'Y' );
1078     if( !$startM ) $startM = date( 'm' );
1079     if( !$startD ) $startD = date( 'd' );
1080     $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
1081     if( !$endY )   $endY   = $startY;
1082     if( !$endM )   $endM   = $startM;
1083     if( !$endD )   $endD   = $startD;
1084     $endDate   = mktime( 23, 59, 59, $endM, $endD, $endY );
1085 // echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br />\n"; $tcnt = 0;// test ###
1086             /* check component types */
1087     $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
1088     if( is_array( $cType )) {
1089       foreach( $cType as $cix => $theType ) {
1090         $cType[$cix] = $theType = strtolower( $theType );
1091         if( !in_array( $theType, $validTypes ))
1092           $cType[$cix] = 'vevent';
1093       }
1094       $cType = array_unique( $cType );
1095     }
1096     elseif( !empty( $cType )) {
1097       $cType = strtolower( $cType );
1098       if( !in_array( $cType, $validTypes ))
1099         $cType = array( 'vevent' );
1100       else
1101         $cType = array( $cType );
1102     }
1103     else
1104       $cType = $validTypes;
1105     if( 0 >= count( $cType ))
1106       $cType = $validTypes;
1107     if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
1108       $split = FALSE;
1109     if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
1110       $split = FALSE;
1111             /* iterate components */
1112     $result       = array();
1113     $this->sort( 'UID' );
1114     $compUIDcmp   = null;
1115     $recurridList = array();
1116     foreach ( $this->components as $cix => $component ) {
1117       if( empty( $component )) continue;
1118       unset( $start );
1119             /* deselect unvalid type components */
1120       if( !in_array( $component->objName, $cType ))
1121         continue;
1122       $start = $component->getProperty( 'dtstart' );
1123             /* select due when dtstart is missing */
1124       if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' ))))
1125         continue;
1126       if( empty( $start ))
1127         continue;
1128       $compUID      = $component->getProperty( 'UID' );
1129       if( $compUIDcmp != $compUID ) {
1130         $compUIDcmp = $compUID;
1131         unset( $exdatelist, $recurridList );
1132       }
1133       $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE;
1134       unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $workstart, $workend, $endDateFormat ); // clean up
1135       $startWdate = iCalUtilityFunctions::_date2timestamp( $start );
1136       $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1137             /* get end date from dtend/due/duration properties */
1138       $end = $component->getProperty( 'dtend' );
1139       if( !empty( $end )) {
1140         $dtendExist = TRUE;
1141         $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1142       }
1143       if( empty( $end ) && ( $component->objName == 'vtodo' )) {
1144         $end = $component->getProperty( 'due' );
1145         if( !empty( $end )) {
1146           $dueExist = TRUE;
1147           $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1148         }
1149       }
1150       if( !empty( $end ) && !isset( $end['hour'] )) {
1151           /* a DTEND without time part regards an event that ends the day before,
1152              for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
1153         $endAllDayEvent = TRUE;
1154         $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
1155         $end['year']  = date( 'Y', $endWdate );
1156         $end['month'] = date( 'm', $endWdate );
1157         $end['day']   = date( 'd', $endWdate );
1158         $end['hour']  = 23;
1159         $end['min']   = $end['sec'] = 59;
1160       }
1161       if( empty( $end )) {
1162         $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
1163         if( !empty( $end ))
1164           $durationExist = TRUE;
1165           $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1166 // if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
1167       }
1168       if( empty( $end )) { // assume one day duration if missing end date
1169         $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
1170       }
1171 // if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
1172       $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
1173       if( $endWdate < $startWdate ) { // MUST be after start date!!
1174         $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
1175         $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
1176       }
1177       $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds
1178             /* make a list of optional exclude dates for component occurence from exrule and exdate */
1179       $exdatelist = array();
1180       $workstart  = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
1181       $workend    = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
1182       while( FALSE !== ( $exrule = $component->getProperty( 'exrule' )))    // check exrule
1183         iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
1184       while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) {  // check exdate
1185         foreach( $exdate as $theExdate ) {
1186           $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
1187           $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
1188           if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
1189             $exdatelist[$exWdate] = TRUE;
1190         } // end - foreach( $exdate as $theExdate )
1191       }  // end - check exdate
1192             /* check recurrence-id (note, a missing sequence=0, don't test foer sequence), remove hit with reccurr-id date */
1193       if( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) {
1194 // echo "adding ".$recurrid['year'].'-'.$recurrid['month'].'-'.$recurrid['day']." to recurridList<br>\n"; // test ###
1195         $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
1196         $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
1197         $recurridList[$recurrid] = TRUE; // no recurring to start this day
1198       } // end recurrence-id/sequence test
1199             /* select only components with.. . */
1200       if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
+1201          (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period
1202             /* add the selected component (WITHIN valid dates) to output array */
1203         if( $flat ) { // any=true/false, ignores split
1204           if( !$recurrid )
1205             $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
1206         }
1207         elseif( $split ) { // split the original component
1208           if( $endWdate > $endDate )
1209             $endWdate = $endDate;     // use period end date
1210           $rstart   = $startWdate;
1211           if( $rstart < $startDate )
1212             $rstart = $startDate; // use period start date
1213           $startYMD = $rstartYMD = date( 'Ymd', $rstart );
1214           $endYMD   = date( 'Ymd', $endWdate );
1215           $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1216 // echo "start org comp = $rstartYMD, endYMD=$endYMD<br />\n"; // test ###
1217           if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
1218             while( $rstartYMD <= $endYMD ) { // iterate
1219               if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
1220                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1221                 $rstartYMD = date( 'Ymd', $rstart );
1222                 continue;
1223               }
1224               if( $rstartYMD > $startYMD ) // date after dtstart
1225                 $datestring = date( $startDateFormat, $checkDate ); // mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
1226               else
1227                 $datestring = date( $startDateFormat, $rstart );
1228               if( isset( $start['tz'] ))
1229                 $datestring .= ' '.$start['tz'];
1230 // echo "split org comp rstartYMD=$rstartYMD (datestring=$datestring)<br />\n"; // test ###
1231               $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
1232               if( $dtendExist || $dueExist || $durationExist ) {
1233                 if( $rstartYMD < $endYMD ) // not the last day
1234                   $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1235                 else
1236                   $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1237                 if( $endAllDayEvent && $dtendExist )
1238                   $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1239                 $datestring = date( $endDateFormat, $tend );
1240                 if( isset( $end['tz'] ))
1241                   $datestring .= ' '.$end['tz'];
1242                 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1243                 $component->setProperty( $propName, $datestring );
1244               } // end if( $dtendExist || $dueExist || $durationExist )
1245               $wd        = getdate( $rstart );
1246               $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
1247               $rstart    = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1248               $rstartYMD = date( 'Ymd', $rstart );
1249               $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1250             } // end while( $rstart <= $endWdate )
1251           }
1252         } // end elseif( $split )   -  else use component date
1253         elseif( $recurrid && !$flat && !$any && !$split )
1254           $continue = TRUE;
1255         else { // !$flat && !$split, i.e. no flat array and DTSTART within period
1256           $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
1257           if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
1258             $wd = getdate( $startWdate );
1259             $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
1260           }
1261         }
1262       } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
1263             /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
1264       if( TRUE === $any ) {
1265             /* make a list of optional repeating dates for component occurence, rrule, rdate */
1266         $recurlist = array();
1267         while( FALSE !== ( $rrule = $component->getProperty( 'rrule' )))    // check rrule
1268           iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
1269         foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
1270           $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
1271         while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) {  // check rdate
1272           foreach( $rdate as $theRdate ) {
1273             if( is_array( $theRdate ) && ( 2 == count( $theRdate )) &&  // all days within PERIOD
1274                    array_key_exists( '0', $theRdate ) &&  array_key_exists( '1', $theRdate )) {
1275               $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
1276               if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
1277                 continue;
1278               if( isset( $theRdate[1]['year'] )) // date-date period
1279                 $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
1280               else {                             // date-duration period
1281                 $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
1282                 $rend = iCalUtilityFunctions::_date2timestamp( $rend );
1283               }
1284               while( $rstart < $rend ) {
1285                 $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
1286                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1287               }
1288             } // PERIOD end
1289             else { // single date
1290               $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
1291               if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
1292                 $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
1293             }
1294           }
1295         }  // end - check rdate
1296         foreach( $recurlist as $recurkey => $durvalue ) { // remove all recurrence START dates found in the exdatelist
1297           $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
1298           if( isset( $exdatelist[$checkDate] )) // no recurring to start this day
1299             unset( $recurlist[$recurkey] );
1300         }
1301         if( 0 < count( $recurlist )) {
1302           ksort( $recurlist );
1303           $xRecurrence = 1;
1304           $component2  = $component->copy();
1305           $compUID     = $component2->getProperty( 'UID' );
1306           foreach( $recurlist as $recurkey => $durvalue ) {
1307 // echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###;
1308             if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
1309               continue;
1310             $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
1311             if( isset( $recurridList[$checkDate] )) // no recurring to start this day
1312               continue;
1313             if( isset( $exdatelist[$checkDate] )) // check excluded dates
1314               continue;
1315             if( $startWdate >= $recurkey ) // exclude component start date
1316               continue;
1317             $rstart = $recurkey;
1318             $rend   = $recurkey + $durvalue;
1319            /* add repeating components within valid dates to output array, only start date set */
1320             if( $flat ) {
1321               if( !isset( $result[$compUID] )) // only one comp
1322                 $result[$compUID] = $component2->copy(); // copy to output
1323             }
1324            /* add repeating components within valid dates to output array, one each day */
1325             elseif( $split ) {
1326               $xRecurrence += 1;
1327               if( $rend > $endDate )
1328                 $rend = $endDate;
1329               $startYMD = $rstartYMD = date( 'Ymd', $rstart );
1330               $endYMD   = date( 'Ymd', $rend );
1331 // echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br />\n"; // test ###;
1332               while( $rstart <= $rend ) { // iterate.. .
1333                 $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1334                 if( isset( $recurridList[$checkDate] )) // no recurring to start this day
1335                   break;
1336                 if( isset( $exdatelist[$checkDate] ))  // exclude any recurrence START date, found in exdatelist
1337                   break;
1338 // echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br />"; // test ###;
1339                 if( $rstart >= $startDate ) {    // date after dtstart
1340                   if( $rstartYMD > $startYMD ) // date after dtstart
1341                     $datestring = date( $startDateFormat, $checkDate );
1342                   else
1343                     $datestring = date( $startDateFormat, $rstart );
1344                   if( isset( $start['tz'] ))
1345                     $datestring .= ' '.$start['tz'];
1346 // echo "spliting = $datestring<BR>\n"; // test ###
1347                   $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1348                   if( $dtendExist || $dueExist || $durationExist ) {
1349                     if( $rstartYMD < $endYMD ) // not the last day
1350                       $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1351                     else
1352                       $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1353                     if( $endAllDayEvent && $dtendExist )
1354                       $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1355                     $datestring = date( $endDateFormat, $tend );
1356                     if( isset( $end['tz'] ))
1357                       $datestring .= ' '.$end['tz'];
1358                     $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1359                     $component2->setProperty( $propName, $datestring );
1360                   } // end if( $dtendExist || $dueExist || $durationExist )
1361                   $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1362                   $wd = getdate( $rstart );
1363                   $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
1364                 } // end if( $checkDate > $startYMD ) {    // date after dtstart
1365                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1366                 $rstartYMD = date( 'Ymd', $rstart );
1367               } // end while( $rstart <= $rend )
1368             } // end elseif( $split )
1369             elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *//
1370               $xRecurrence += 1;
1371               $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1372               if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
1373                 $datestring = date( $startDateFormat, $rstart );
1374                 if( isset( $start['tz'] ))
1375                   $datestring .= ' '.$start['tz'];
1376 //echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
1377                 $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1378                 if( $dtendExist || $dueExist || $durationExist ) {
1379                   $tend = $rstart + $rdurWsecs;
1380                   if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate ))
1381                     $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ));
1382                   else
1383                     $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!!
1384                   if( $endAllDayEvent && $dtendExist )
1385                     $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1386                   $datestring = date( $endDateFormat, $tend );
1387                   if( isset( $end['tz'] ))
1388                     $datestring .= ' '.$end['tz'];
1389                   $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1390                   $component2->setProperty( $propName, $datestring );
1391                 } // end if( $dtendExist || $dueExist || $durationExist )
1392                 $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1393                 $wd = getdate( $rstart );
1394                 $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
1395               } // end if( !isset( $exdatelist[$checkDate] ))
1396             } // end elseif( $rstart >= $startDate )
1397           } // end foreach( $recurlist as $recurkey => $durvalue )
1398           unset( $component2 );
1399         } // end if( 0 < count( $recurlist ))
1400             /* deselect components with startdate/enddate not within period */
1401         if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
1402           continue;
1403       } // end if( TRUE === $any )
1404     } // end foreach ( $this->components as $cix => $component )
1405     unset( $dtendExist, $dueExist, $durationExist, $endAllDayEvent, $recurrid, $recurridList,
+1406            $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $recurlist, $workstart, $workend, $endDateFormat ); // clean up
1407     if( 0 >= count( $result )) return FALSE;
1408     elseif( !$flat ) {
1409       foreach( $result as $y => $yeararr ) {
1410         foreach( $yeararr as $m => $montharr ) {
1411           foreach( $montharr as $d => $dayarr ) {
1412             if( empty( $result[$y][$m][$d] ))
1413                 unset( $result[$y][$m][$d] );
1414             else
1415               $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
1416           }
1417           if( empty( $result[$y][$m] ))
1418               unset( $result[$y][$m] );
1419           else
1420             ksort( $result[$y][$m] );
1421         }
1422         if( empty( $result[$y] ))
1423             unset( $result[$y] );
1424         else
1425           ksort( $result[$y] );
1426       }
1427       if( empty( $result ))
1428           unset( $result );
1429       else
1430         ksort( $result );
1431     } // end elseif( !$flat )
1432     if( 0 >= count( $result ))
1433       return FALSE;
1434     return $result;
1435   }
1436 /**
1437  * select components from calendar on based on specific property value(-s)
1438  *
1439  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1440  * @since 2.16.6 - 2012-12-26
1441  * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
1442  * @return array
1443  */
1444   function selectComponents2( $selectOptions ) {
1445     $output = array();
1446     $allowedComps      = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
1447     $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
1448     foreach( $this->components as $cix => $component3 ) {
1449       if( !in_array( $component3->objName, $allowedComps ))
1450         continue;
1451       $uid = $component3->getProperty( 'UID' );
1452       foreach( $selectOptions as $propName => $pvalue ) {
1453         $propName = strtoupper( $propName );
1454         if( !in_array( $propName, $allowedProperties ))
1455           continue;
1456         if( !is_array( $pvalue ))
1457           $pvalue = array( $pvalue );
1458         if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
1459           $output[$uid][] = $component3->copy();
1460           continue;
1461         }
1462         elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'CONTACT' == $propName ) || ( 'RELATED-TO' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple occurrence?
1463           $propValues = array();
1464           $component3->_getProperties( $propName, $propValues );
1465           $propValues = array_keys( $propValues );
1466           foreach( $pvalue as $theValue ) {
1467             if( in_array( $theValue, $propValues )) { //  && !isset( $output[$uid] )) {
1468               $output[$uid][] = $component3->copy();
1469               break;
1470             }
1471           }
1472           continue;
1473         } // end   elseif( // multiple occurrence?
1474         elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence
1475           continue;
1476         if( is_array( $d )) {
1477           foreach( $d as $part ) {
1478             if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
1479               $output[$uid][] = $component3->copy();
1480           }
1481         }
1482         elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
1483           foreach( $pvalue as $pval ) {
1484             if( FALSE !== stripos( $d, $pval )) {
1485               $output[$uid][] = $component3->copy();
1486               break;
1487             }
1488           }
1489         }
1490         elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
1491           $output[$uid][] = $component3->copy();
1492       } // end foreach( $selectOptions as $propName => $pvalue ) {
1493     } // end foreach( $this->components as $cix => $component3 ) {
1494     if( !empty( $output )) {
1495       ksort( $output ); // uid order
1496       $output2 = array();
1497       foreach( $output as $uid => $components ) {
1498         foreach( $components as $component )
1499           $output2[] = $component;
1500       }
1501       $output = $output2;
1502     }
1503     return $output;
1504   }
1505 /**
1506  * add calendar component to container
1507  *
1508  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1509  * @since 2.8.8 - 2011-03-15
1510  * @param object $component calendar component
1511  * @param mixed $arg1 optional, ordno/component type/ component uid
1512  * @param mixed $arg2 optional, ordno if arg1 = component type
1513  * @return void
1514  */
1515   function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
1516     $component->setConfig( $this->getConfig(), FALSE, TRUE );
1517     if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
1518             /* make sure dtstamp and uid is set */
1519       $dummy1 = $component->getProperty( 'dtstamp' );
1520       $dummy2 = $component->getProperty( 'uid' );
1521     }
1522     if( !$arg1 ) { // plain insert, last in chain
1523       $this->components[] = $component->copy();
1524       return TRUE;
1525     }
1526     $argType = $index = null;
1527     if ( ctype_digit( (string) $arg1 )) { // index insert/replace
1528       $argType = 'INDEX';
1529       $index   = (int) $arg1 - 1;
1530     }
1531     elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
1532       $argType = strtolower( $arg1 );
1533       $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
1534     }
1535     // else if arg1 is set, arg1 must be an UID
1536     $cix1sC = 0;
1537     foreach ( $this->components as $cix => $component2) {
1538       if( empty( $component2 )) continue;
1539       if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
1540         $this->components[$cix] = $component->copy();
1541         return TRUE;
1542       }
1543       elseif( $argType == $component2->objName ) { // component Type index insert/replace
1544         if( $index == $cix1sC ) {
1545           $this->components[$cix] = $component->copy();
1546           return TRUE;
1547         }
1548         $cix1sC++;
1549       }
1550       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
1551         $this->components[$cix] = $component->copy();
1552         return TRUE;
1553       }
1554     }
1555             /* arg1=index and not found.. . insert at index .. .*/
1556     if( 'INDEX' == $argType ) {
1557       $this->components[$index] = $component->copy();
1558       ksort( $this->components, SORT_NUMERIC );
1559     }
1560     else    /* not found.. . insert last in chain anyway .. .*/
1561       $this->components[] = $component->copy();
1562     return TRUE;
1563   }
1564 /**
1565  * sort iCal compoments
1566  *
1567  * ascending sort on properties (if exist) x-current-dtstart, dtstart,
1568  * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
1569  * otherwise sorting on specific (argument) property values
1570  *
1571  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1572  * @since 2.16.4 - 2012-12-17
1573  * @param string $sortArg, optional
1574  * @return void
1575  *
1576  */
1577   function sort( $sortArg=FALSE ) {
1578     if( is_array( $this->components )) {
1579       if( $sortArg ) {
1580         $sortArg = strtoupper( $sortArg );
1581         if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' )))
1582           $sortArg = FALSE;
1583       }
1584             /* set sort parameters for each component */
1585       foreach( $this->components as $cix => & $c ) {
1586         $c->srtk = array( '0', '0', '0', '0' );
1587         if( 'vtimezone' == $c->objName ) {
1588           if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
1589             $c->srtk[0] = 0;
1590           continue;
1591         }
1592         elseif( $sortArg ) {
1593           if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'CONTACT' == $sortArg ) || ( 'RELATED-TO' == $sortArg ) || ( 'RESOURCES' == $sortArg )) {
1594             $propValues = array();
1595             $c->_getProperties( $sortArg, $propValues );
1596             if( !empty( $propValues )) {
1597               $sk         = array_keys( $propValues );
1598               $c->srtk[0] = $sk[0];
1599               if( 'RELATED-TO'  == $sortArg )
1600                 $c->srtk[0] .= $c->getProperty( 'uid' );
1601             }
1602             elseif( 'RELATED-TO'  == $sortArg )
1603               $c->srtk[0] = $c->getProperty( 'uid' );
1604           }
1605           elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) {
1606             $c->srtk[0] = $d;
1607             if( 'UID' == $sortArg ) {
1608               if( FALSE !== ( $d = $c->getProperty( 'recurrence-id' ))) {
1609                 $c->srtk[1] = iCalUtilityFunctions::_date2strdate( $d );
1610                 if( FALSE === ( $c->srtk[2] = $c->getProperty( 'sequence' )))
1611                   $c->srtk[2] = PHP_INT_MAX;
1612               }
1613               else
1614                 $c->srtk[1] = $c->srtk[2] = PHP_INT_MAX;
1615             }
1616           }
1617           continue;
1618         }
1619         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
1620           $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] );
1621           unset( $c->srtk[0]['unparsedtext'] );
1622         }
1623         elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
1624           $c->srtk[1] = 0;                                                  // sortkey 0 : dtstart
1625         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
1626           $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration)
1627           unset( $c->srtk[1]['unparsedtext'] );
1628         }
1629         elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
1630           if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
1631             $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );
1632             unset( $c->srtk[1]['unparsedtext'] );
1633           }
1634           elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
1635             if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
1636               $c->srtk[1] = 0;
1637         }
1638         if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
1639           if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
1640             $c->srtk[2] = 0;
1641         if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
1642           $c->srtk[3] = 0;
1643       } // end foreach( $this->components as & $c
1644             /* sort */
1645       usort( $this->components, array( 'iCalUtilityFunctions', '_cmpfcn' ));
1646     }
1647   }
1648 /**
1649  * parse iCal text/file into vcalendar, components, properties and parameters
1650  *
1651  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1652  * @since 2.16.2 - 2012-12-18
1653  * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
1654  * @return bool FALSE if error occurs during parsing
1655  *
1656  */
1657   function parse( $unparsedtext=FALSE ) {
1658     $nl = $this->getConfig( 'nl' );
1659     if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
1660             /* directory+filename is set previously via setConfig directory+filename or url */
1661       if( FALSE === ( $filename = $this->getConfig( 'url' )))
1662         $filename = $this->getConfig( 'dirfile' );
1663             /* READ FILE */
1664       if( FALSE === ( $rows = file_get_contents( $filename )))
1665         return FALSE;                 /* err 1 */
1666     }
1667     elseif( is_array( $unparsedtext ))
1668       $rows =  implode( '\n'.$nl, $unparsedtext );
1669     else
1670       $rows = & $unparsedtext;
1671             /* fix line folding */
1672     $rows = explode( $nl, iCalUtilityFunctions::convEolChar( $rows, $nl ));
1673             /* skip leading (empty/invalid) lines */
1674     foreach( $rows as $lix => $line ) {
1675       if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' ))
1676         break;
1677       unset( $rows[$lix] );
1678     }
1679     $rcnt = count( $rows );
1680     if( 3 > $rcnt )                  /* err 10 */
1681       return FALSE;
1682             /* skip trailing empty lines and ensure an end row */
1683     $lix  = array_keys( $rows );
1684     $lix  = end( $lix );
1685     while( 3 < $lix ) {
1686       $tst = trim( $rows[$lix] );
1687       if(( '\n' == $tst ) || empty( $tst )) {
1688         unset( $rows[$lix] );
1689         $lix--;
1690         continue;
1691       }
1692       if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' ))
1693         $rows[] = 'END:VCALENDAR';
1694       break;
1695     }
1696     $comp    = & $this;
1697     $calsync = $compsync = 0;
1698             /* identify components and update unparsed data within component */
1699     $config = $this->getConfig();
1700     $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' );
1701     foreach( $rows as $lix => $line ) {
1702       if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
1703         $calsync++;
1704         continue;
1705       }
1706       elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
1707         if( 0 < $compsync )
1708           $this->components[] = $comp->copy();
1709         $compsync--;
1710         $calsync--;
1711         break;
1712       }
1713       elseif( 1 != $calsync )
1714         return FALSE;                 /* err 20 */
1715       elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) {
1716         $this->components[] = $comp->copy();
1717         $compsync--;
1718         continue;
1719       }
1720       if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) {
1721         $comp = new vevent( $config );
1722         $compsync++;
1723       }
1724       elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) {
1725         $comp = new vfreebusy( $config );
1726         $compsync++;
1727       }
1728       elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) {
1729         $comp = new vjournal( $config );
1730         $compsync++;
1731       }
1732       elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) {
1733         $comp = new vtodo( $config );
1734         $compsync++;
1735       }
1736       elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) {
1737         $comp = new vtimezone( $config );
1738         $compsync++;
1739       }
1740       else { /* update component with unparsed data */
1741         $comp->unparsed[] = $line;
1742       }
1743     } // end foreach( $rows as $line )
1744     unset( $config, $endtxt );
1745             /* parse data for calendar (this) object */
1746     if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
1747             /* concatenate property values spread over several lines */
1748       $propnames = array( 'calscale','method','prodid','version','x-' );
1749       $proprows  = array();
1750       for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
1751         $line = rtrim( $this->unparsed[$i], $nl );
1752         while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
1753           $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
1754         $proprows[] = $line;
1755       }
1756       $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
1757       $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
1758       $paramProto4 = array( 'crid:', 'news:', 'pres:' );
1759       foreach( $proprows as $line ) {
1760         if( '\n' == substr( $line, -2 ))
1761           $line = substr( $line, 0, -2 );
1762             /* get property name */
1763         $propname  = '';
1764         $cix       = 0;
1765         while( FALSE !== ( $char = substr( $line, $cix, 1 ))) {
1766           if( in_array( $char, array( ':', ';' )))
1767             break;
1768           else
1769             $propname .= $char;
1770           $cix++;
1771         }
1772             /* skip non standard property names */
1773         if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames ))
1774           continue;
1775             /* ignore version/prodid properties */
1776         if( in_array( strtolower( $propname ), array( 'version', 'prodid' )))
1777           continue;
1778             /* rest of the line is opt.params and value */
1779         $line = substr( $line, $cix);
1780             /* separate attributes from value */
1781         $attr         = array();
1782         $attrix       = -1;
1783         $strlen       = strlen( $line );
1784         $WithinQuotes = FALSE;
1785         $cix          = 0;
1786         while( FALSE !== substr( $line, $cix, 1 )) {
1787           if(                       ( ':'  == $line[$cix] )                         &&
+1788                                     ( substr( $line,$cix,     3 )  != '://' )       &&
+1789              ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
+1790              ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
+1791              ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
+1792                         ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
1793                !$WithinQuotes ) {
1794             $attrEnd = TRUE;
1795             if(( $cix < ( $strlen - 4 )) &&
+1796                  ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
1797               for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
1798                 if( '://' == substr( $line, $c2ix - 2, 3 )) {
1799                   $attrEnd = FALSE;
1800                   break; // an URI with a portnr!!
1801                 }
1802               }
1803             }
1804             if( $attrEnd) {
1805               $line = substr( $line, ( $cix + 1 ));
1806               break;
1807             }
1808           }
1809           if( '"' == $line[$cix] )
1810             $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
1811           if( ';' == $line[$cix] )
1812             $attr[++$attrix] = null;
1813           else
1814             $attr[$attrix] .= $line[$cix];
1815           $cix++;
1816         }
1817             /* make attributes in array format */
1818         $propattr = array();
1819         foreach( $attr as $attribute ) {
1820           $attrsplit = explode( '=', $attribute, 2 );
1821           if( 1 < count( $attrsplit ))
1822             $propattr[$attrsplit[0]] = $attrsplit[1];
1823           else
1824             $propattr[] = $attribute;
1825         }
1826             /* update Property */
1827         if( FALSE !== strpos( $line, ',' )) {
1828           $content  = array( 0 => '' );
1829           $cix = $lix = 0;
1830           while( FALSE !== substr( $line, $lix, 1 )) {
1831             if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
1832               $cix++;
1833               $content[$cix] = '';
1834             }
1835             else
1836               $content[$cix] .= $line[$lix];
1837             $lix++;
1838           }
1839           if( 1 < count( $content )) {
1840             foreach( $content as $cix => $contentPart )
1841               $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
1842             $this->setProperty( $propname, $content, $propattr );
1843             continue;
1844           }
1845           else
1846             $line = reset( $content );
1847           $line = iCalUtilityFunctions::_strunrep( $line );
1848         }
1849         $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr );
1850       } // end - foreach( $this->unparsed.. .
1851     } // end - if( is_array( $this->unparsed.. .
1852     unset( $unparsedtext, $rows, $this->unparsed, $proprows );
1853             /* parse Components */
1854     if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
1855       $ckeys = array_keys( $this->components );
1856       foreach( $ckeys as $ckey ) {
1857         if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
1858           $this->components[$ckey]->parse();
1859         }
1860       }
1861     }
1862     else
1863       return FALSE;                   /* err 91 or something.. . */
1864     return TRUE;
1865   }
1866 /*********************************************************************************/
1867 /**
1868  * creates formatted output for calendar object instance
1869  *
1870  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1871  * @since 2.10.16 - 2011-10-28
1872  * @return string
1873  */
1874   function createCalendar() {
1875     $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
1876     switch( $this->format ) {
1877       case 'xcal':
1878         $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
+1879                          '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
+1880                          '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
1881         $calendarStart = '>'.$this->nl.'<vcalendar';
1882         break;
1883       default:
1884         $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
1885         break;
1886     }
1887     $calendarStart .= $this->createVersion();
1888     $calendarStart .= $this->createProdid();
1889     $calendarStart .= $this->createCalscale();
1890     $calendarStart .= $this->createMethod();
1891     if( 'xcal' == $this->format )
1892       $calendarStart .= '>'.$this->nl;
1893     $calendar .= $this->createXprop();
1894 
1895     foreach( $this->components as $component ) {
1896       if( empty( $component )) continue;
1897       $component->setConfig( $this->getConfig(), FALSE, TRUE );
1898       $calendar .= $component->createComponent( $this->xcaldecl );
1899     }
1900     if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
1901       $calendarInit .= ' [';
1902       $old_xcaldecl  = array();
1903       foreach( $this->xcaldecl as $declix => $declPart ) {
1904         if(( 0 < count( $old_xcaldecl))    &&
1905              isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
1906              isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
+1907            ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
+1908            ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
1909           continue; // no duplicate uri and ext. references
1910         if(( 0 < count( $old_xcaldecl))    &&
1911             !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
1912              isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
+1913            ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
1914           continue; // no duplicate element declarations
1915         $calendarxCaldecl .= $this->nl.'<!';
1916         foreach( $declPart as $declKey => $declValue ) {
1917           switch( $declKey ) {                    // index
1918             case 'xmldecl':                       // no 1
1919               $calendarxCaldecl .= $declValue.' ';
1920               break;
1921             case 'uri':                           // no 2
1922               $calendarxCaldecl .= $declValue.' ';
1923               $old_xcaldecl['uri'][] = $declValue;
1924               break;
1925             case 'ref':                           // no 3
1926               $calendarxCaldecl .= $declValue.' ';
1927               $old_xcaldecl['ref'][] = $declValue;
1928               break;
1929             case 'external':                      // no 4
1930               $calendarxCaldecl .= '"'.$declValue.'" ';
1931               $old_xcaldecl['external'][] = $declValue;
1932               break;
1933             case 'type':                          // no 5
1934               $calendarxCaldecl .= $declValue.' ';
1935               break;
1936             case 'type2':                         // no 6
1937               $calendarxCaldecl .= $declValue;
1938               break;
1939           }
1940         }
1941         $calendarxCaldecl .= '>';
1942       }
1943       $calendarxCaldecl .= $this->nl.']';
1944     }
1945     switch( $this->format ) {
1946       case 'xcal':
1947         $calendar .= '</vcalendar>'.$this->nl;
1948         break;
1949       default:
1950         $calendar .= 'END:VCALENDAR'.$this->nl;
1951         break;
1952     }
1953     return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
1954   }
1955 /**
1956  * a HTTP redirect header is sent with created, updated and/or parsed calendar
1957  *
1958  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1959  * @since 2.10.24 - 2011-12-23
1960  * @param bool $utf8Encode
1961  * @param bool $gzip
1962  * @return redirect
1963  */
1964   function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
1965     $filename = $this->getConfig( 'filename' );
1966     $output   = $this->createCalendar();
1967     if( $utf8Encode )
1968       $output = utf8_encode( $output );
1969     if( $gzip ) {
1970       $output = gzencode( $output, 9 );
1971       header( 'Content-Encoding: gzip' );
1972       header( 'Vary: *' );
1973       header( 'Content-Length: '.strlen( $output ));
1974     }
1975     if( 'xcal' == $this->format )
1976       header( 'Content-Type: application/calendar+xml; charset=utf-8' );
1977     else
1978       header( 'Content-Type: text/calendar; charset=utf-8' );
1979     header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
1980     header( 'Cache-Control: max-age=10' );
1981     die( $output );
1982   }
1983 /**
1984  * save content in a file
1985  *
1986  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1987  * @since 2.2.12 - 2007-12-30
1988  * @param string $directory optional
1989  * @param string $filename optional
1990  * @param string $delimiter optional
1991  * @return bool
1992  */
1993   function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
1994     if( $directory )
1995       $this->setConfig( 'directory', $directory );
1996     if( $filename )
1997       $this->setConfig( 'filename',  $filename );
1998     if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
1999       $this->setConfig( 'delimiter', $delimiter );
2000     if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
2001       $dirfile = $this->getConfig( 'dirfile' );
2002     $iCalFile = @fopen( $dirfile, 'w' );
2003     if( $iCalFile ) {
2004       if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
2005         return FALSE;
2006       fclose( $iCalFile );
2007       return TRUE;
2008     }
2009     else
2010       return FALSE;
2011   }
2012 /**
2013  * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
2014  * else FALSE is returned
2015  *
2016  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2017  * @since 2.2.12 - 2007-10-28
2018  * @param string $directory optional alt. int timeout
2019  * @param string $filename optional
2020  * @param string $delimiter optional
2021  * @param int timeout optional, default 3600 sec
2022  * @return redirect/FALSE
2023  */
2024   function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
2025     if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
2026       $timeout   = (int) $directory;
2027       $directory = FALSE;
2028     }
2029     if( $directory )
2030       $this->setConfig( 'directory', $directory );
2031     if( $filename )
2032       $this->setConfig( 'filename',  $filename );
2033     if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
2034       $this->setConfig( 'delimiter', $delimiter );
2035     $filesize    = $this->getConfig( 'filesize' );
2036     if( 0 >= $filesize )
2037       return FALSE;
2038     $dirfile     = $this->getConfig( 'dirfile' );
2039     if( time() - filemtime( $dirfile ) < $timeout) {
2040       clearstatcache();
2041       $dirfile   = $this->getConfig( 'dirfile' );
2042       $filename  = $this->getConfig( 'filename' );
2043 //    if( headers_sent( $filename, $linenum ))
2044 //      die( "Headers already sent in $filename on line $linenum\n" );
2045       if( 'xcal' == $this->format )
2046         header( 'Content-Type: application/calendar+xml; charset=utf-8' );
2047       else
2048         header( 'Content-Type: text/calendar; charset=utf-8' );
2049       header( 'Content-Length: '.$filesize );
2050       header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
2051       header( 'Cache-Control: max-age=10' );
2052       $fp = @fopen( $dirfile, 'r' );
2053       if( $fp ) {
2054         fpassthru( $fp );
2055         fclose( $fp );
2056       }
2057       die();
2058     }
2059     else
2060       return FALSE;
2061   }
2062 }
2063 /*********************************************************************************/
2064 /*********************************************************************************/
2065 /**
2066  *  abstract class for calendar components
2067  *
2068  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2069  * @since 2.9.6 - 2011-05-14
2070  */
2071 class calendarComponent {
2072             //  component property variables
2073   var $uid;
2074   var $dtstamp;
2075 
2076             //  component config variables
2077   var $allowEmpty;
2078   var $language;
2079   var $nl;
2080   var $unique_id;
2081   var $format;
2082   var $objName; // created automatically at instance creation
2083   var $dtzid;   // default (local) timezone
2084             //  component internal variables
2085   var $componentStart1;
2086   var $componentStart2;
2087   var $componentEnd1;
2088   var $componentEnd2;
2089   var $elementStart1;
2090   var $elementStart2;
2091   var $elementEnd1;
2092   var $elementEnd2;
2093   var $intAttrDelimiter;
2094   var $attributeDelimiter;
2095   var $valueInit;
2096             //  component xCal declaration container
2097   var $xcaldecl;
2098 /**
2099  * constructor for calendar component object
2100  *
2101  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2102  * @since 2.9.6 - 2011-05-17
2103  */
2104   function calendarComponent() {
2105     $this->objName         = ( isset( $this->timezonetype )) ?
2106                           strtolower( $this->timezonetype )  :  get_class ( $this );
2107     $this->uid             = array();
2108     $this->dtstamp         = array();
2109 
2110     $this->language        = null;
2111     $this->nl              = null;
2112     $this->unique_id       = null;
2113     $this->format          = null;
2114     $this->dtzid           = null;
2115     $this->allowEmpty      = TRUE;
2116     $this->xcaldecl        = array();
2117 
2118     $this->_createFormat();
2119     $this->_makeDtstamp();
2120   }
2121 /*********************************************************************************/
2122 /**
2123  * Property Name: ACTION
2124  */
2125 /**
2126  * creates formatted output for calendar component property action
2127  *
2128  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2129  * @since 2.4.8 - 2008-10-22
2130  * @return string
2131  */
2132   function createAction() {
2133     if( empty( $this->action )) return FALSE;
2134     if( empty( $this->action['value'] ))
2135       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
2136     $attributes = $this->_createParams( $this->action['params'] );
2137     return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
2138   }
2139 /**
2140  * set calendar component property action
2141  *
2142  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2143  * @since 2.4.8 - 2008-11-04
2144  * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
2145  * @param mixed $params
2146  * @return bool
2147  */
2148   function setAction( $value, $params=FALSE ) {
2149     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2150     $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
2151     return TRUE;
2152   }
2153 /*********************************************************************************/
2154 /**
2155  * Property Name: ATTACH
2156  */
2157 /**
2158  * creates formatted output for calendar component property attach
2159  *
2160  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2161  * @since 2.11.16 - 2012-02-04
2162  * @return string
2163  */
2164   function createAttach() {
2165     if( empty( $this->attach )) return FALSE;
2166     $output       = null;
2167     foreach( $this->attach as $attachPart ) {
2168       if( !empty( $attachPart['value'] )) {
2169         $attributes = $this->_createParams( $attachPart['params'] );
2170         if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
2171           $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
2172           $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
2173           $output     = substr( $str, 0, 75 ).$this->nl;
2174           $str        = substr( $str, 75 );
2175           $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' );
2176           if( ' ' == substr( $output, -1 ))
2177             $output   = rtrim( $output );
2178           if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
2179             $output  .= $this->nl;
2180           return $output;
2181         }
2182         $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
2183       }
2184       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
2185     }
2186     return $output;
2187   }
2188 /**
2189  * set calendar component property attach
2190  *
2191  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2192  * @since 2.5.1 - 2008-11-06
2193  * @param string $value
2194  * @param array $params, optional
2195  * @param integer $index, optional
2196  * @return bool
2197  */
2198   function setAttach( $value, $params=FALSE, $index=FALSE ) {
2199     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2200     iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
2201     return TRUE;
2202   }
2203 /*********************************************************************************/
2204 /**
2205  * Property Name: ATTENDEE
2206  */
2207 /**
2208  * creates formatted output for calendar component property attendee
2209  *
2210  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2211  * @since 2.11.12 - 2012-01-31
2212  * @return string
2213  */
2214   function createAttendee() {
2215     if( empty( $this->attendee )) return FALSE;
2216     $output = null;
2217     foreach( $this->attendee as $attendeePart ) {                      // start foreach 1
2218       if( empty( $attendeePart['value'] )) {
2219         if( $this->getConfig( 'allowEmpty' ))
2220           $output .= $this->_createElement( 'ATTENDEE' );
2221         continue;
2222       }
2223       $attendee1 = $attendee2 = null;
2224       foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
2225         if( 'value' == $paramlabel )
2226           $attendee2     .= $paramvalue;
2227         elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
2228           $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
2229           foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes
2230             if( is_array( $pValue ) || in_array( $pKey, $mParams ))
2231               continue;
2232             if(( FALSE !== strpos( $pValue, ':' )) ||
+2233                ( FALSE !== strpos( $pValue, ';' )) ||
+2234                ( FALSE !== strpos( $pValue, ',' )))
2235               $paramvalue[$pKey] = '"'.$pValue.'"';
2236           }
2237         // set attenddee parameters in rfc2445 order
2238           if( isset( $paramvalue['CUTYPE'] ))
2239             $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
2240           if( isset( $paramvalue['MEMBER'] )) {
2241             $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
2242             foreach( $paramvalue['MEMBER'] as $cix => $opv )
2243               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2244           }
2245           if( isset( $paramvalue['ROLE'] ))
2246             $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
2247           if( isset( $paramvalue['PARTSTAT'] ))
2248             $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
2249           if( isset( $paramvalue['RSVP'] ))
2250             $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
2251           if( isset( $paramvalue['DELEGATED-TO'] )) {
2252             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
2253             foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
2254               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2255           }
2256           if( isset( $paramvalue['DELEGATED-FROM'] )) {
2257             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
2258             foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
2259               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2260           }
2261           if( isset( $paramvalue['SENT-BY'] ))
2262             $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
2263           if( isset( $paramvalue['CN'] ))
2264             $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
2265           if( isset( $paramvalue['DIR'] )) {
2266             $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
2267             $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
2268           }
2269           if( isset( $paramvalue['LANGUAGE'] ))
2270             $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
2271           $xparams = array();
2272           foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
2273             if( ctype_digit( (string) $optparamlabel )) {
2274               $xparams[]  = $optparamvalue;
2275               continue;
2276             }
2277             if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
2278               $xparams[$optparamlabel] = $optparamvalue;
2279           } // end foreach 3
2280           ksort( $xparams, SORT_STRING );
2281           foreach( $xparams as $paramKey => $paramValue ) {
2282             if( ctype_digit( (string) $paramKey ))
2283               $attendee1 .= $this->intAttrDelimiter.$paramValue;
2284             else
2285               $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
2286           }      // end foreach 3
2287         }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
2288       }          // end foreach 2
2289       $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
2290     }              // end foreach 1
2291     return $output;
2292   }
2293 /**
2294  * set calendar component property attach
2295  *
2296  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2297  * @since 2.12.18 - 2012-07-13
2298  * @param string $value
2299  * @param array $params, optional
2300  * @param integer $index, optional
2301  * @return bool
2302  */
2303   function setAttendee( $value, $params=FALSE, $index=FALSE ) {
2304     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2305           // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
2306     if( !empty( $value )) {
2307       if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
2308         $value = 'MAILTO:'.$value;
2309       elseif( !empty( $value ))
2310         $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
2311       $value = str_replace( 'mailto:', 'MAILTO:', $value );
2312     }
2313     $params2 = array();
2314     if( is_array($params )) {
2315       $optarrays = array();
2316       foreach( $params as $optparamlabel => $optparamvalue ) {
2317         $optparamlabel = strtoupper( $optparamlabel );
2318         switch( $optparamlabel ) {
2319           case 'MEMBER':
2320           case 'DELEGATED-TO':
2321           case 'DELEGATED-FROM':
2322             if( !is_array( $optparamvalue ))
2323               $optparamvalue = array( $optparamvalue );
2324             foreach( $optparamvalue as $part ) {
2325               $part = trim( $part );
2326               if(( '"' == substr( $part, 0, 1 )) &&
+2327                  ( '"' == substr( $part, -1 )))
2328                 $part = substr( $part, 1, ( strlen( $part ) - 2 ));
2329               if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
2330                 $part = "MAILTO:$part";
2331               else
2332                 $part = 'MAILTO:'.substr( $part, 7 );
2333               $optarrays[$optparamlabel][] = $part;
2334             }
2335             break;
2336           default:
2337             if(( '"' == substr( $optparamvalue, 0, 1 )) &&
+2338                ( '"' == substr( $optparamvalue, -1 )))
2339               $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
2340             if( 'SENT-BY' ==  $optparamlabel ) {
2341               if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
2342                 $optparamvalue = "MAILTO:$optparamvalue";
2343               else
2344                 $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
2345             }
2346             $params2[$optparamlabel] = $optparamvalue;
2347             break;
2348         } // end switch( $optparamlabel.. .
2349       } // end foreach( $optparam.. .
2350       foreach( $optarrays as $optparamlabel => $optparams )
2351         $params2[$optparamlabel] = $optparams;
2352     }
2353         // remove defaults
2354     iCalUtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
2355     iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
2356     iCalUtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
2357     iCalUtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
2358         // check language setting
2359     if( isset( $params2['CN' ] )) {
2360       $lang = $this->getConfig( 'language' );
2361       if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
2362         $params2['LANGUAGE' ] = $lang;
2363     }
2364     iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
2365     return TRUE;
2366   }
2367 /*********************************************************************************/
2368 /**
2369  * Property Name: CATEGORIES
2370  */
2371 /**
2372  * creates formatted output for calendar component property categories
2373  *
2374  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2375  * @since 2.16.2 - 2012-12-18
2376  * @return string
2377  */
2378   function createCategories() {
2379     if( empty( $this->categories )) return FALSE;
2380     $output = null;
2381     foreach( $this->categories as $category ) {
2382       if( empty( $category['value'] )) {
2383         if ( $this->getConfig( 'allowEmpty' ))
2384           $output .= $this->_createElement( 'CATEGORIES' );
2385         continue;
2386       }
2387       $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
2388       if( is_array( $category['value'] )) {
2389         foreach( $category['value'] as $cix => $categoryPart )
2390           $category['value'][$cix] = iCalUtilityFunctions::_strrep( $categoryPart, $this->format, $this->nl );
2391         $content  = implode( ',', $category['value'] );
2392       }
2393       else
2394         $content  = iCalUtilityFunctions::_strrep( $category['value'], $this->format, $this->nl );
2395       $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
2396     }
2397     return $output;
2398   }
2399 /**
2400  * set calendar component property categories
2401  *
2402  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2403  * @since 2.5.1 - 2008-11-06
2404  * @param mixed $value
2405  * @param array $params, optional
2406  * @param integer $index, optional
2407  * @return bool
2408  */
2409   function setCategories( $value, $params=FALSE, $index=FALSE ) {
2410     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2411     iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
2412     return TRUE;
2413  }
2414 /*********************************************************************************/
2415 /**
2416  * Property Name: CLASS
2417  */
2418 /**
2419  * creates formatted output for calendar component property class
2420  *
2421  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2422  * @since 0.9.7 - 2006-11-20
2423  * @return string
2424  */
2425   function createClass() {
2426     if( empty( $this->class )) return FALSE;
2427     if( empty( $this->class['value'] ))
2428       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
2429     $attributes = $this->_createParams( $this->class['params'] );
2430     return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
2431   }
2432 /**
2433  * set calendar component property class
2434  *
2435  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2436  * @since 2.4.8 - 2008-11-04
2437  * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
2438  * @param array $params optional
2439  * @return bool
2440  */
2441   function setClass( $value, $params=FALSE ) {
2442     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2443     $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
2444     return TRUE;
2445   }
2446 /*********************************************************************************/
2447 /**
2448  * Property Name: COMMENT
2449  */
2450 /**
2451  * creates formatted output for calendar component property comment
2452  *
2453  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2454  * @since 2.16.2 - 2012-12-18
2455  * @return string
2456  */
2457   function createComment() {
2458     if( empty( $this->comment )) return FALSE;
2459     $output = null;
2460     foreach( $this->comment as $commentPart ) {
2461       if( empty( $commentPart['value'] )) {
2462         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
2463         continue;
2464       }
2465       $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
2466       $content    = iCalUtilityFunctions::_strrep( $commentPart['value'], $this->format, $this->nl );
2467       $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
2468     }
2469     return $output;
2470   }
2471 /**
2472  * set calendar component property comment
2473  *
2474  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2475  * @since 2.5.1 - 2008-11-06
2476  * @param string $value
2477  * @param array $params, optional
2478  * @param integer $index, optional
2479  * @return bool
2480  */
2481   function setComment( $value, $params=FALSE, $index=FALSE ) {
2482     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2483     iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
2484     return TRUE;
2485   }
2486 /*********************************************************************************/
2487 /**
2488  * Property Name: COMPLETED
2489  */
2490 /**
2491  * creates formatted output for calendar component property completed
2492  *
2493  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2494  * @since 2.4.8 - 2008-10-22
2495  * @return string
2496  */
2497   function createCompleted( ) {
2498     if( empty( $this->completed )) return FALSE;
2499     if( !isset( $this->completed['value']['year'] )  &&
2500         !isset( $this->completed['value']['month'] ) &&
2501         !isset( $this->completed['value']['day'] )   &&
2502         !isset( $this->completed['value']['hour'] )  &&
2503         !isset( $this->completed['value']['min'] )   &&
2504         !isset( $this->completed['value']['sec'] ))
2505       if( $this->getConfig( 'allowEmpty' ))
2506         return $this->_createElement( 'COMPLETED' );
2507       else return FALSE;
2508     $formatted  = iCalUtilityFunctions::_date2strdate( $this->completed['value'], 7 );
2509     $attributes = $this->_createParams( $this->completed['params'] );
2510     return $this->_createElement( 'COMPLETED', $attributes, $formatted );
2511   }
2512 /**
2513  * set calendar component property completed
2514  *
2515  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2516  * @since 2.4.8 - 2008-10-23
2517  * @param mixed $year
2518  * @param mixed $month optional
2519  * @param int $day optional
2520  * @param int $hour optional
2521  * @param int $min optional
2522  * @param int $sec optional
2523  * @param array $params optional
2524  * @return bool
2525  */
2526   function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2527     if( empty( $year )) {
2528       if( $this->getConfig( 'allowEmpty' )) {
2529         $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2530         return TRUE;
2531       }
2532       else
2533         return FALSE;
2534     }
2535     $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2536     return TRUE;
2537   }
2538 /*********************************************************************************/
2539 /**
2540  * Property Name: CONTACT
2541  */
2542 /**
2543  * creates formatted output for calendar component property contact
2544  *
2545  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2546  * @since 2.16.2 - 2012-12-18
2547  * @return string
2548  */
2549   function createContact() {
2550     if( empty( $this->contact )) return FALSE;
2551     $output = null;
2552     foreach( $this->contact as $contact ) {
2553       if( !empty( $contact['value'] )) {
2554         $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
2555         $content    = iCalUtilityFunctions::_strrep( $contact['value'], $this->format, $this->nl );
2556         $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
2557       }
2558       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
2559     }
2560     return $output;
2561   }
2562 /**
2563  * set calendar component property contact
2564  *
2565  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2566  * @since 2.5.1 - 2008-11-05
2567  * @param string $value
2568  * @param array $params, optional
2569  * @param integer $index, optional
2570  * @return bool
2571  */
2572   function setContact( $value, $params=FALSE, $index=FALSE ) {
2573     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2574     iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
2575     return TRUE;
2576   }
2577 /*********************************************************************************/
2578 /**
2579  * Property Name: CREATED
2580  */
2581 /**
2582  * creates formatted output for calendar component property created
2583  *
2584  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2585  * @since 2.4.8 - 2008-10-21
2586  * @return string
2587  */
2588   function createCreated() {
2589     if( empty( $this->created )) return FALSE;
2590     $formatted  = iCalUtilityFunctions::_date2strdate( $this->created['value'], 7 );
2591     $attributes = $this->_createParams( $this->created['params'] );
2592     return $this->_createElement( 'CREATED', $attributes, $formatted );
2593   }
2594 /**
2595  * set calendar component property created
2596  *
2597  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2598  * @since 2.4.8 - 2008-10-23
2599  * @param mixed $year optional
2600  * @param mixed $month optional
2601  * @param int $day optional
2602  * @param int $hour optional
2603  * @param int $min optional
2604  * @param int $sec optional
2605  * @param mixed $params optional
2606  * @return bool
2607  */
2608   function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2609     if( !isset( $year )) {
2610       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
2611     }
2612     $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2613     return TRUE;
2614   }
2615 /*********************************************************************************/
2616 /**
2617  * Property Name: DESCRIPTION
2618  */
2619 /**
2620  * creates formatted output for calendar component property description
2621  *
2622  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2623  * @since 2.16.2 - 2012-12-18
2624  * @return string
2625  */
2626   function createDescription() {
2627     if( empty( $this->description )) return FALSE;
2628     $output       = null;
2629     foreach( $this->description as $description ) {
2630       if( !empty( $description['value'] )) {
2631         $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
2632         $content    = iCalUtilityFunctions::_strrep( $description['value'], $this->format, $this->nl );
2633         $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
2634       }
2635       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
2636     }
2637     return $output;
2638   }
2639 /**
2640  * set calendar component property description
2641  *
2642  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2643  * @since 2.6.24 - 2010-11-06
2644  * @param string $value
2645  * @param array $params, optional
2646  * @param integer $index, optional
2647  * @return bool
2648  */
2649   function setDescription( $value, $params=FALSE, $index=FALSE ) {
2650     if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; }
2651     if( 'vjournal' != $this->objName )
2652       $index = 1;
2653     iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
2654     return TRUE;
2655   }
2656 /*********************************************************************************/
2657 /**
2658  * Property Name: DTEND
2659  */
2660 /**
2661  * creates formatted output for calendar component property dtend
2662  *
2663  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2664  * @since 2.14.4 - 2012-09-26
2665  * @return string
2666  */
2667   function createDtend() {
2668     if( empty( $this->dtend )) return FALSE;
2669     if( !isset( $this->dtend['value']['year'] )  &&
2670         !isset( $this->dtend['value']['month'] ) &&
2671         !isset( $this->dtend['value']['day'] )   &&
2672         !isset( $this->dtend['value']['hour'] )  &&
2673         !isset( $this->dtend['value']['min'] )   &&
2674         !isset( $this->dtend['value']['sec'] ))
2675       if( $this->getConfig( 'allowEmpty' ))
2676         return $this->_createElement( 'DTEND' );
2677       else return FALSE;
2678     $parno      = ( isset( $this->dtend['params']['VALUE'] ) && ( 'DATE' == $this->dtend['params']['VALUE'] )) ? 3 : null;
2679     $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtend['value'], $parno );
2680     $attributes = $this->_createParams( $this->dtend['params'] );
2681     return $this->_createElement( 'DTEND', $attributes, $formatted );
2682   }
2683 /**
2684  * set calendar component property dtend
2685  *
2686  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2687  * @since 2.9.6 - 2011-05-14
2688  * @param mixed $year
2689  * @param mixed $month optional
2690  * @param int $day optional
2691  * @param int $hour optional
2692  * @param int $min optional
2693  * @param int $sec optional
2694  * @param string $tz optional
2695  * @param array params optional
2696  * @return bool
2697  */
2698   function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2699     if( empty( $year )) {
2700       if( $this->getConfig( 'allowEmpty' )) {
2701         $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2702         return TRUE;
2703       }
2704       else
2705         return FALSE;
2706     }
2707     $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
2708     return TRUE;
2709   }
2710 /*********************************************************************************/
2711 /**
2712  * Property Name: DTSTAMP
2713  */
2714 /**
2715  * creates formatted output for calendar component property dtstamp
2716  *
2717  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2718  * @since 2.4.4 - 2008-03-07
2719  * @return string
2720  */
2721   function createDtstamp() {
2722     if( !isset( $this->dtstamp['value']['year'] )  &&
2723         !isset( $this->dtstamp['value']['month'] ) &&
2724         !isset( $this->dtstamp['value']['day'] )   &&
2725         !isset( $this->dtstamp['value']['hour'] )  &&
2726         !isset( $this->dtstamp['value']['min'] )   &&
2727         !isset( $this->dtstamp['value']['sec'] ))
2728       $this->_makeDtstamp();
2729     $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstamp['value'], 7 );
2730     $attributes = $this->_createParams( $this->dtstamp['params'] );
2731     return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
2732   }
2733 /**
2734  * computes datestamp for calendar component object instance dtstamp
2735  *
2736  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2737  * @since 2.14.1 - 2012-09-29
2738  * @return void
2739  */
2740   function _makeDtstamp() {
2741     $d    = date( 'Y-m-d-H-i-s', mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')));
2742     $date = explode( '-', $d );
2743     $this->dtstamp['value'] = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2], 'hour' => $date[3], 'min' => $date[4], 'sec' => $date[5], 'tz' => 'Z' );
2744     $this->dtstamp['params'] = null;
2745   }
2746 /**
2747  * set calendar component property dtstamp
2748  *
2749  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2750  * @since 2.4.8 - 2008-10-23
2751  * @param mixed $year
2752  * @param mixed $month optional
2753  * @param int $day optional
2754  * @param int $hour optional
2755  * @param int $min optional
2756  * @param int $sec optional
2757  * @param array $params optional
2758  * @return TRUE
2759  */
2760   function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2761     if( empty( $year ))
2762       $this->_makeDtstamp();
2763     else
2764       $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2765     return TRUE;
2766   }
2767 /*********************************************************************************/
2768 /**
2769  * Property Name: DTSTART
2770  */
2771 /**
2772  * creates formatted output for calendar component property dtstart
2773  *
2774  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2775  * @since 2.14.4 - 2012-09-26
2776  * @return string
2777  */
2778   function createDtstart() {
2779     if( empty( $this->dtstart )) return FALSE;
2780     if( !isset( $this->dtstart['value']['year'] )  &&
2781         !isset( $this->dtstart['value']['month'] ) &&
2782         !isset( $this->dtstart['value']['day'] )   &&
2783         !isset( $this->dtstart['value']['hour'] )  &&
2784         !isset( $this->dtstart['value']['min'] )   &&
2785         !isset( $this->dtstart['value']['sec'] )) {
2786       if( $this->getConfig( 'allowEmpty' ))
2787         return $this->_createElement( 'DTSTART' );
2788       else return FALSE;
2789     }
2790     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
2791        unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
2792     $parno      = ( isset( $this->dtstart['params']['VALUE'] ) && ( 'DATE' == $this->dtstart['params']['VALUE'] )) ? 3 : null;
2793     $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstart['value'], $parno );
2794     $attributes = $this->_createParams( $this->dtstart['params'] );
2795     return $this->_createElement( 'DTSTART', $attributes, $formatted );
2796   }
2797 /**
2798  * set calendar component property dtstart
2799  *
2800  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2801  * @since 2.6.22 - 2010-09-22
2802  * @param mixed $year
2803  * @param mixed $month optional
2804  * @param int $day optional
2805  * @param int $hour optional
2806  * @param int $min optional
2807  * @param int $sec optional
2808  * @param string $tz optional
2809  * @param array $params optional
2810  * @return bool
2811  */
2812   function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2813     if( empty( $year )) {
2814       if( $this->getConfig( 'allowEmpty' )) {
2815         $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2816         return TRUE;
2817       }
2818       else
2819         return FALSE;
2820     }
2821     $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
2822     return TRUE;
2823   }
2824 /*********************************************************************************/
2825 /**
2826  * Property Name: DUE
2827  */
2828 /**
2829  * creates formatted output for calendar component property due
2830  *
2831  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2832  * @since 2.14.4 - 2012-09-26
2833  * @return string
2834  */
2835   function createDue() {
2836     if( empty( $this->due )) return FALSE;
2837     if( !isset( $this->due['value']['year'] )  &&
2838         !isset( $this->due['value']['month'] ) &&
2839         !isset( $this->due['value']['day'] )   &&
2840         !isset( $this->due['value']['hour'] )  &&
2841         !isset( $this->due['value']['min'] )   &&
2842         !isset( $this->due['value']['sec'] )) {
2843       if( $this->getConfig( 'allowEmpty' ))
2844         return $this->_createElement( 'DUE' );
2845       else
2846        return FALSE;
2847     }
2848     $parno      = ( isset( $this->due['params']['VALUE'] ) && ( 'DATE' == $this->due['params']['VALUE'] )) ? 3 : null;
2849     $formatted  = iCalUtilityFunctions::_date2strdate( $this->due['value'], $parno );
2850     $attributes = $this->_createParams( $this->due['params'] );
2851     return $this->_createElement( 'DUE', $attributes, $formatted );
2852   }
2853 /**
2854  * set calendar component property due
2855  *
2856  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2857  * @since 2.4.8 - 2008-11-04
2858  * @param mixed $year
2859  * @param mixed $month optional
2860  * @param int $day optional
2861  * @param int $hour optional
2862  * @param int $min optional
2863  * @param int $sec optional
2864  * @param array $params optional
2865  * @return bool
2866  */
2867   function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2868     if( empty( $year )) {
2869       if( $this->getConfig( 'allowEmpty' )) {
2870         $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2871         return TRUE;
2872       }
2873       else
2874         return FALSE;
2875     }
2876     $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
2877     return TRUE;
2878   }
2879 /*********************************************************************************/
2880 /**
2881  * Property Name: DURATION
2882  */
2883 /**
2884  * creates formatted output for calendar component property duration
2885  *
2886  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2887  * @since 2.4.8 - 2008-10-21
2888  * @return string
2889  */
2890   function createDuration() {
2891     if( empty( $this->duration )) return FALSE;
2892     if( !isset( $this->duration['value']['week'] ) &&
2893         !isset( $this->duration['value']['day'] )  &&
2894         !isset( $this->duration['value']['hour'] ) &&
2895         !isset( $this->duration['value']['min'] )  &&
2896         !isset( $this->duration['value']['sec'] ))
2897       if( $this->getConfig( 'allowEmpty' ))
2898         return $this->_createElement( 'DURATION', array(), null );
2899       else return FALSE;
2900     $attributes = $this->_createParams( $this->duration['params'] );
2901     return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_duration2str( $this->duration['value'] ));
2902   }
2903 /**
2904  * set calendar component property duration
2905  *
2906  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2907  * @since 2.4.8 - 2008-11-04
2908  * @param mixed $week
2909  * @param mixed $day optional
2910  * @param int $hour optional
2911  * @param int $min optional
2912  * @param int $sec optional
2913  * @param array $params optional
2914  * @return bool
2915  */
2916   function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2917     if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE;
2918     if( is_array( $week ) && ( 1 <= count( $week )))
2919       $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
2920     elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
2921       $week = trim( $week );
2922       if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
2923         $week = substr( $week, 1 );
2924       $this->duration = array( 'value' => iCalUtilityFunctions::_durationStr2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
2925     }
2926     elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec ))
2927       return FALSE;
2928     else
2929       $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params ));
2930     return TRUE;
2931   }
2932 /*********************************************************************************/
2933 /**
2934  * Property Name: EXDATE
2935  */
2936 /**
2937  * creates formatted output for calendar component property exdate
2938  *
2939  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2940  * @since 2.16.5 - 2012-12-28
2941  * @return string
2942  */
2943   function createExdate() {
2944     if( empty( $this->exdate )) return FALSE;
2945     $output  = null;
2946     $exdates = array();
2947     foreach( $this->exdate as $theExdate ) {
2948       if( empty( $theExdate['value'] )) {
2949         if( $this->getConfig( 'allowEmpty' ))
2950           $output .= $this->_createElement( 'EXDATE' );
2951         continue;
2952       }
2953       if( 1 < count( $theExdate['value'] ))
2954         usort( $theExdate['value'], array( 'iCalUtilityFunctions', '_sortExdate1' ));
2955       $exdates[] = $theExdate;
2956     }
2957     if( 1 < count( $exdates ))
2958       usort( $exdates, array( 'iCalUtilityFunctions', '_sortExdate2' ));
2959     foreach( $exdates as $theExdate ) {
2960       $content = $attributes = null;
2961       foreach( $theExdate['value'] as $eix => $exdatePart ) {
2962         $parno = count( $exdatePart );
2963         $formatted = iCalUtilityFunctions::_date2strdate( $exdatePart, $parno );
2964         if( isset( $theExdate['params']['TZID'] ))
2965           $formatted = str_replace( 'Z', '', $formatted);
2966         if( 0 < $eix ) {
2967           if( isset( $theExdate['value'][0]['tz'] )) {
2968             if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
+2969                ( 'Z' == $theExdate['value'][0]['tz'] )) {
2970               if( 'Z' != substr( $formatted, -1 ))
2971                 $formatted .= 'Z';
2972             }
2973             else
2974               $formatted = str_replace( 'Z', '', $formatted );
2975           }
2976           else
2977             $formatted = str_replace( 'Z', '', $formatted );
2978         } // end if( 0 < $eix )
2979         $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
2980       } // end foreach( $theExdate['value'] as $eix => $exdatePart )
2981       $attributes .= $this->_createParams( $theExdate['params'] );
2982       $output .= $this->_createElement( 'EXDATE', $attributes, $content );
2983     } // end foreach( $exdates as $theExdate )
2984     return $output;
2985   }
2986 /**
2987  * set calendar component property exdate
2988  *
2989  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2990  * @since 2.14.1 - 2012-10-02
2991  * @param array exdates
2992  * @param array $params, optional
2993  * @param integer $index, optional
2994  * @return bool
2995  */
2996   function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
2997     if( empty( $exdates )) {
2998       if( $this->getConfig( 'allowEmpty' )) {
2999         iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index );
3000         return TRUE;
3001       }
3002       else
3003         return FALSE;
3004     }
3005     $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
3006     $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
3007             /* ev. check 1:st date and save ev. timezone **/
3008     iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
3009     iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
3010     foreach( $exdates as $eix => $theExdate ) {
3011       iCalUtilityFunctions::_strDate2arr( $theExdate );
3012       if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) {
3013         if( isset( $theExdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theExdate['tz'] )) {
3014           if( isset( $input['params']['TZID'] ))
3015             $theExdate['tz'] = $input['params']['TZID'];
3016           else
3017             $input['params']['TZID'] = $theExdate['tz'];
3018         }
3019         $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
3020       }
3021       elseif(  is_array( $theExdate )) {
3022         $d = iCalUtilityFunctions::_chkDateArr( $theExdate, $parno );
3023         if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
3024           $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
3025           $exdatea = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
3026           unset( $exdatea['unparsedtext'] );
3027         }
3028         else
3029           $exdatea = $d;
3030       }
3031       elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
3032         $exdatea = iCalUtilityFunctions::_strdate2date( $theExdate, $parno );
3033         unset( $exdatea['unparsedtext'] );
3034       }
3035       if( 3 == $parno )
3036         unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
3037       elseif( isset( $exdatea['tz'] ))
3038         $exdatea['tz'] = (string) $exdatea['tz'];
3039       if(  isset( $input['params']['TZID'] ) ||
+3040          ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
+3041          ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
+3042          ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
3043         unset( $exdatea['tz'] );
3044       if( $toZ ) // time zone Z
3045         $exdatea['tz'] = 'Z';
3046       $input['value'][] = $exdatea;
3047     }
3048     if( 0 >= count( $input['value'] ))
3049       return FALSE;
3050     if( 3 == $parno ) {
3051       $input['params']['VALUE'] = 'DATE';
3052       unset( $input['params']['TZID'] );
3053     }
3054     if( $toZ ) // time zone Z
3055       unset( $input['params']['TZID'] );
3056     iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
3057     return TRUE;
3058   }
3059 /*********************************************************************************/
3060 /**
3061  * Property Name: EXRULE
3062  */
3063 /**
3064  * creates formatted output for calendar component property exrule
3065  *
3066  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3067  * @since 2.4.8 - 2008-10-22
3068  * @return string
3069  */
3070   function createExrule() {
3071     if( empty( $this->exrule )) return FALSE;
3072     return $this->_format_recur( 'EXRULE', $this->exrule );
3073   }
3074 /**
3075  * set calendar component property exdate
3076  *
3077  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3078  * @since 2.5.1 - 2008-11-05
3079  * @param array $exruleset
3080  * @param array $params, optional
3081  * @param integer $index, optional
3082  * @return bool
3083  */
3084   function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
3085     if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE;
3086     iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
3087     return TRUE;
3088   }
3089 /*********************************************************************************/
3090 /**
3091  * Property Name: FREEBUSY
3092  */
3093 /**
3094  * creates formatted output for calendar component property freebusy
3095  *
3096  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3097  * @since 2.1.23 - 2012-02-16
3098  * @return string
3099  */
3100   function createFreebusy() {
3101     if( empty( $this->freebusy )) return FALSE;
3102     $output = null;
3103     foreach( $this->freebusy as $freebusyPart ) {
3104       if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
3105         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
3106         continue;
3107       }
3108       $attributes = $content = null;
3109       if( isset( $freebusyPart['value']['fbtype'] )) {
3110           $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
3111         unset( $freebusyPart['value']['fbtype'] );
3112         $freebusyPart['value'] = array_values( $freebusyPart['value'] );
3113       }
3114       else
3115         $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
3116       $attributes .= $this->_createParams( $freebusyPart['params'] );
3117       $fno = 1;
3118       $cnt = count( $freebusyPart['value']);
3119       foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
3120         $formatted   = iCalUtilityFunctions::_date2strdate( $freebusyPeriod[0] );
3121         $content .= $formatted;
3122         $content .= '/';
3123         $cnt2 = count( $freebusyPeriod[1]);
3124         if( array_key_exists( 'year', $freebusyPeriod[1] ))      // date-time
3125           $cnt2 = 7;
3126         elseif( array_key_exists( 'week', $freebusyPeriod[1] ))  // duration
3127           $cnt2 = 5;
3128         if(( 7 == $cnt2 )   &&    // period=  -> date-time
3129             isset( $freebusyPeriod[1]['year'] )  &&
3130             isset( $freebusyPeriod[1]['month'] ) &&
3131             isset( $freebusyPeriod[1]['day'] )) {
3132           $content .= iCalUtilityFunctions::_date2strdate( $freebusyPeriod[1] );
3133         }
3134         else {                                  // period=  -> dur-time
3135           $content .= iCalUtilityFunctions::_duration2str( $freebusyPeriod[1] );
3136         }
3137         if( $fno < $cnt )
3138           $content .= ',';
3139         $fno++;
3140       }
3141       $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
3142     }
3143     return $output;
3144   }
3145 /**
3146  * set calendar component property freebusy
3147  *
3148  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3149  * @since 2.10.30 - 2012-01-16
3150  * @param string $fbType
3151  * @param array $fbValues
3152  * @param array $params, optional
3153  * @param integer $index, optional
3154  * @return bool
3155  */
3156   function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
3157     if( empty( $fbValues )) {
3158       if( $this->getConfig( 'allowEmpty' )) {
3159         iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index );
3160         return TRUE;
3161       }
3162       else
3163         return FALSE;
3164     }
3165     $fbType = strtoupper( $fbType );
3166     if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
+3167        ( 'X-' != substr( $fbType, 0, 2 )))
3168       $fbType = 'BUSY';
3169     $input = array( 'fbtype' => $fbType );
3170     foreach( $fbValues as $fbPeriod ) {   // periods => period
3171       if( empty( $fbPeriod ))
3172         continue;
3173       $freebusyPeriod = array();
3174       foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
3175         $freebusyPairMember = array();
3176         if( is_array( $fbMember )) {
3177           if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
3178             $freebusyPairMember       = iCalUtilityFunctions::_chkDateArr( $fbMember, 7 );
3179             $freebusyPairMember['tz'] = 'Z';
3180           }
3181           elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
3182             $freebusyPairMember       = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
3183             $freebusyPairMember['tz'] = 'Z';
3184           }
3185           else {                                         // array format duration
3186             $freebusyPairMember = iCalUtilityFunctions::_duration2arr( $fbMember );
3187           }
3188         }
3189         elseif(( 3 <= strlen( trim( $fbMember ))) &&    // string format duration
+3190                ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
3191           if( 'P' != $fbMember{0} )
3192             $fbmember = substr( $fbMember, 1 );
3193           $freebusyPairMember = iCalUtilityFunctions::_durationStr2arr( $fbMember );
3194         }
3195         elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
3196           $freebusyPairMember       = iCalUtilityFunctions::_strdate2date( $fbMember, 7 );
3197           unset( $freebusyPairMember['unparsedtext'] );
3198           $freebusyPairMember['tz'] = 'Z';
3199         }
3200         $freebusyPeriod[]   = $freebusyPairMember;
3201       }
3202       $input[]              = $freebusyPeriod;
3203     }
3204     iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
3205     return TRUE;
3206   }
3207 /*********************************************************************************/
3208 /**
3209  * Property Name: GEO
3210  */
3211 /**
3212  * creates formatted output for calendar component property geo
3213  *
3214  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3215  * @since 2.12.6 - 2012-04-21
3216  * @return string
3217  */
3218   function createGeo() {
3219     if( empty( $this->geo )) return FALSE;
3220     if( empty( $this->geo['value'] ))
3221       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
3222     $attributes = $this->_createParams( $this->geo['params'] );
3223     if( 0.0 < $this->geo['value']['latitude'] )
3224       $sign   = '+';
3225     else
3226       $sign   = ( 0.0 > $this->geo['value']['latitude'] ) ? '-' : '';
3227     $content  = $sign.sprintf( "%09.6f", abs( $this->geo['value']['latitude'] ));       // sprintf && lpad && float && sign !"#¤%&/(
3228     $content  = rtrim( rtrim( $content, '0' ), '.' );
3229     if( 0.0 < $this->geo['value']['longitude'] )
3230       $sign   = '+';
3231     else
3232       $sign   = ( 0.0 > $this->geo['value']['longitude'] ) ? '-' : '';
3233     $content .= ';'.$sign.sprintf( '%8.6f', abs( $this->geo['value']['longitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
3234     $content  = rtrim( rtrim( $content, '0' ), '.' );
3235     return $this->_createElement( 'GEO', $attributes, $content );
3236   }
3237 /**
3238  * set calendar component property geo
3239  *
3240  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3241  * @since 2.12.5 - 2012-04-21
3242  * @param float $latitude
3243  * @param float $longitude
3244  * @param array $params optional
3245  * @return bool
3246  */
3247   function setGeo( $latitude, $longitude, $params=FALSE ) {
3248     if(( !empty( $latitude )  || ( 0 == $latitude )) &&
+3249        ( !empty( $longitude ) || ( 0 == $longitude ))) {
3250       if( !is_array( $this->geo )) $this->geo = array();
3251       $this->geo['value']['latitude']  = (float) $latitude;
3252       $this->geo['value']['longitude'] = (float) $longitude;
3253       $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
3254     }
3255     elseif( $this->getConfig( 'allowEmpty' ))
3256       $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
3257     else
3258       return FALSE;
3259     return TRUE;
3260   }
3261 /*********************************************************************************/
3262 /**
3263  * Property Name: LAST-MODIFIED
3264  */
3265 /**
3266  * creates formatted output for calendar component property last-modified
3267  *
3268  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3269  * @since 2.4.8 - 2008-10-21
3270  * @return string
3271  */
3272   function createLastModified() {
3273     if( empty( $this->lastmodified )) return FALSE;
3274     $attributes = $this->_createParams( $this->lastmodified['params'] );
3275     $formatted  = iCalUtilityFunctions::_date2strdate( $this->lastmodified['value'], 7 );
3276     return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
3277   }
3278 /**
3279  * set calendar component property completed
3280  *
3281  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3282  * @since 2.4.8 - 2008-10-23
3283  * @param mixed $year optional
3284  * @param mixed $month optional
3285  * @param int $day optional
3286  * @param int $hour optional
3287  * @param int $min optional
3288  * @param int $sec optional
3289  * @param array $params optional
3290  * @return boll
3291  */
3292   function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
3293     if( empty( $year ))
3294       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
3295     $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
3296     return TRUE;
3297   }
3298 /*********************************************************************************/
3299 /**
3300  * Property Name: LOCATION
3301  */
3302 /**
3303  * creates formatted output for calendar component property location
3304  *
3305  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3306  * @since 2.16.2 - 2012-12-18
3307  * @return string
3308  */
3309   function createLocation() {
3310     if( empty( $this->location )) return FALSE;
3311     if( empty( $this->location['value'] ))
3312       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
3313     $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
3314     $content    = iCalUtilityFunctions::_strrep( $this->location['value'], $this->format, $this->nl );
3315     return $this->_createElement( 'LOCATION', $attributes, $content );
3316   }
3317 /**
3318  * set calendar component property location
3319  '
3320  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3321  * @since 2.4.8 - 2008-11-04
3322  * @param string $value
3323  * @param array params optional
3324  * @return bool
3325  */
3326   function setLocation( $value, $params=FALSE ) {
3327     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3328     $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3329     return TRUE;
3330   }
3331 /*********************************************************************************/
3332 /**
3333  * Property Name: ORGANIZER
3334  */
3335 /**
3336  * creates formatted output for calendar component property organizer
3337  *
3338  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3339  * @since 2.6.33 - 2010-12-17
3340  * @return string
3341  */
3342   function createOrganizer() {
3343     if( empty( $this->organizer )) return FALSE;
3344     if( empty( $this->organizer['value'] ))
3345       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
3346     $attributes = $this->_createParams( $this->organizer['params']
+3347                                       , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
3348     return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
3349   }
3350 /**
3351  * set calendar component property organizer
3352  *
3353  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3354  * @since 2.12.18 - 2012-07-13
3355  * @param string $value
3356  * @param array params optional
3357  * @return bool
3358  */
3359   function setOrganizer( $value, $params=FALSE ) {
3360     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3361     if( !empty( $value )) {
3362       if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
3363         $value = 'MAILTO:'.$value;
3364       elseif( !empty( $value ))
3365         $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
3366       $value = str_replace( 'mailto:', 'MAILTO:', $value );
3367     }
3368     $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3369     if( isset( $this->organizer['params']['SENT-BY'] )){
3370       if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
3371         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
3372       else
3373         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
3374     }
3375     return TRUE;
3376   }
3377 /*********************************************************************************/
3378 /**
3379  * Property Name: PERCENT-COMPLETE
3380  */
3381 /**
3382  * creates formatted output for calendar component property percent-complete
3383  *
3384  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3385  * @since 2.9.3 - 2011-05-14
3386  * @return string
3387  */
3388   function createPercentComplete() {
3389     if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
3390     if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
3391       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
3392     $attributes = $this->_createParams( $this->percentcomplete['params'] );
3393     return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
3394   }
3395 /**
3396  * set calendar component property percent-complete
3397  *
3398  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3399  * @since 2.9.3 - 2011-05-14
3400  * @param int $value
3401  * @param array $params optional
3402  * @return bool
3403  */
3404   function setPercentComplete( $value, $params=FALSE ) {
3405     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3406     $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3407     return TRUE;
3408   }
3409 /*********************************************************************************/
3410 /**
3411  * Property Name: PRIORITY
3412  */
3413 /**
3414  * creates formatted output for calendar component property priority
3415  *
3416  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3417  * @since 2.9.3 - 2011-05-14
3418  * @return string
3419  */
3420   function createPriority() {
3421     if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
3422     if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
3423       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
3424     $attributes = $this->_createParams( $this->priority['params'] );
3425     return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
3426   }
3427 /**
3428  * set calendar component property priority
3429  *
3430  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3431  * @since 2.9.3 - 2011-05-14
3432  * @param int $value
3433  * @param array $params optional
3434  * @return bool
3435  */
3436   function setPriority( $value, $params=FALSE  ) {
3437     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3438     $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3439     return TRUE;
3440   }
3441 /*********************************************************************************/
3442 /**
3443  * Property Name: RDATE
3444  */
3445 /**
3446  * creates formatted output for calendar component property rdate
3447  *
3448  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3449  * @since 2.16.9 - 2013-01-09
3450  * @return string
3451  */
3452   function createRdate() {
3453     if( empty( $this->rdate )) return FALSE;
3454     $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
3455     $output = null;
3456     $rdates = array();
3457     foreach( $this->rdate as $rpix => $theRdate ) {
3458       if( empty( $theRdate['value'] )) {
3459         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
3460         continue;
3461       }
3462       if( $utctime  )
3463         unset( $theRdate['params']['TZID'] );
3464       if( 1 < count( $theRdate['value'] ))
3465         usort( $theRdate['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
3466       $rdates[] = $theRdate;
3467     }
3468     if( 1 < count( $rdates ))
3469       usort( $rdates, array( 'iCalUtilityFunctions', '_sortRdate2' ));
3470     foreach( $rdates as $rpix => $theRdate ) {
3471       $attributes = $this->_createParams( $theRdate['params'] );
3472       $cnt = count( $theRdate['value'] );
3473       $content = null;
3474       $rno = 1;
3475       foreach( $theRdate['value'] as $rix => $rdatePart ) {
3476         $contentPart = null;
3477         if( is_array( $rdatePart ) &&
3478             isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
3479           if( $utctime )
3480             unset( $rdatePart[0]['tz'] );
3481           $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[0] ); // PERIOD part 1
3482           if( $utctime || !empty( $theRdate['params']['TZID'] ))
3483             $formatted = str_replace( 'Z', '', $formatted);
3484           $contentPart .= $formatted;
3485           $contentPart .= '/';
3486           $cnt2 = count( $rdatePart[1]);
3487           if( array_key_exists( 'year', $rdatePart[1] )) {
3488             if( array_key_exists( 'hour', $rdatePart[1] ))
3489               $cnt2 = 7;                                      // date-time
3490             else
3491               $cnt2 = 3;                                      // date
3492           }
3493           elseif( array_key_exists( 'week', $rdatePart[1] ))  // duration
3494             $cnt2 = 5;
3495           if(( 7 == $cnt2 )   &&    // period=  -> date-time
3496               isset( $rdatePart[1]['year'] )  &&
3497               isset( $rdatePart[1]['month'] ) &&
3498               isset( $rdatePart[1]['day'] )) {
3499             if( $utctime )
3500               unset( $rdatePart[1]['tz'] );
3501             $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[1] ); // PERIOD part 2
3502             if( $utctime || !empty( $theRdate['params']['TZID'] ))
3503               $formatted = str_replace( 'Z', '', $formatted );
3504            $contentPart .= $formatted;
3505           }
3506           else {                                  // period=  -> dur-time
3507             $contentPart .= iCalUtilityFunctions::_duration2str( $rdatePart[1] );
3508           }
3509         } // PERIOD end
3510         else { // SINGLE date start
3511           if( $utctime )
3512             unset( $rdatePart['tz'] );
3513           $parno = ( isset( $theRdate['params']['VALUE'] ) && ( 'DATE' == isset( $theRdate['params']['VALUE'] ))) ? 3 : null;
3514           $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart, $parno );
3515           if( $utctime || !empty( $theRdate['params']['TZID'] ))
3516             $formatted = str_replace( 'Z', '', $formatted);
3517           $contentPart .= $formatted;
3518         }
3519         $content .= $contentPart;
3520         if( $rno < $cnt )
3521           $content .= ',';
3522         $rno++;
3523       }
3524       $output    .= $this->_createElement( 'RDATE', $attributes, $content );
3525     }
3526     return $output;
3527   }
3528 /**
3529  * set calendar component property rdate
3530  *
3531  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3532  * @since 2.14.1 - 2012-10-04
3533  * @param array $rdates
3534  * @param array $params, optional
3535  * @param integer $index, optional
3536  * @return bool
3537  */
3538   function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
3539     if( empty( $rdates )) {
3540       if( $this->getConfig( 'allowEmpty' )) {
3541         iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index );
3542         return TRUE;
3543       }
3544       else
3545         return FALSE;
3546     }
3547     $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
3548     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
3549       unset( $input['params']['TZID'] );
3550       $input['params']['VALUE'] = 'DATE-TIME';
3551     }
3552     $zArr = array( 'GMT', 'UTC', 'Z' );
3553     $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
3554             /*  check if PERIOD, if not set */
3555     if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
3556           isset( $rdates[0] )    && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
3557           isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
+3558     (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
+3559                                       iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
+3560                                     ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
+3561      ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
3562       $input['params']['VALUE'] = 'PERIOD';
3563             /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
3564     $date  = reset( $rdates );
3565     if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
3566       $date  = reset( $date );
3567     iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
3568     iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
3569     foreach( $rdates as $rpix => $theRdate ) {
3570       $inputa = null;
3571       iCalUtilityFunctions::_strDate2arr( $theRdate );
3572       if( is_array( $theRdate )) {
3573         if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
3574           foreach( $theRdate as $rix => $rPeriod ) {
3575             iCalUtilityFunctions::_strDate2arr( $theRdate );
3576             if( is_array( $rPeriod )) {
3577               if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) {    // timestamp
3578                 if( isset( $rPeriod['tz'] ) && !iCalUtilityFunctions::_isOffset( $rPeriod['tz'] )) {
3579                   if( isset( $input['params']['TZID'] ))
3580                     $rPeriod['tz'] = $input['params']['TZID'];
3581                   else
3582                     $input['params']['TZID'] = $rPeriod['tz'];
3583                 }
3584                 $inputab = iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno );
3585               }
3586               elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) {
3587                 $d = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_chkDateArr( $rPeriod, $parno ) : iCalUtilityFunctions::_chkDateArr( $rPeriod, 6 );
3588                 if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
3589                   $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
3590                   $inputab = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
3591                   unset( $inputab['unparsedtext'] );
3592                 }
3593                 else
3594                   $inputab = $d;
3595               }
3596               elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
3597                 $inputab   = iCalUtilityFunctions::_strdate2date( reset( $rPeriod ), $parno );
3598                 unset( $inputab['unparsedtext'] );
3599               }
3600               else                                               // array format duration
3601                 $inputab   = iCalUtilityFunctions::_duration2arr( $rPeriod );
3602             }
3603             elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
+3604                    ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
3605               if( 'P' != $rPeriod[0] )
3606                 $rPeriod   = substr( $rPeriod, 1 );
3607               $inputab     = iCalUtilityFunctions::_durationStr2arr( $rPeriod );
3608             }
3609             elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18
3610               $inputab     = iCalUtilityFunctions::_strdate2date( $rPeriod, $parno );
3611               unset( $inputab['unparsedtext'] );
3612             }
3613             if(( 0 == $rpix ) && ( 0 == $rix )) {
3614               if( isset( $inputab['tz'] ) && in_array( strtoupper( $inputab['tz'] ), $zArr )) {
3615                 $inputab['tz'] = 'Z';
3616                 $toZ = TRUE;
3617               }
3618             }
3619             else {
3620               if( isset( $inputa[0]['tz'] ) && ( 'Z' == $inputa[0]['tz'] ) && isset( $inputab['year'] ))
3621                 $inputab['tz'] = 'Z';
3622               else
3623                 unset( $inputab['tz'] );
3624             }
3625             if( $toZ && isset( $inputab['year'] ) )
3626               $inputab['tz'] = 'Z';
3627             $inputa[]      = $inputab;
3628           }
3629         } // PERIOD end
3630         elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp
3631           if( isset( $theRdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theRdate['tz'] )) {
3632             if( isset( $input['params']['TZID'] ))
3633               $theRdate['tz'] = $input['params']['TZID'];
3634             else
3635               $input['params']['TZID'] = $theRdate['tz'];
3636           }
3637           $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
3638         }
3639         else {                                                                  // date[-time]
3640           $inputa = iCalUtilityFunctions::_chkDateArr( $theRdate, $parno );
3641           if( isset( $inputa['tz'] ) && ( 'Z' != $inputa['tz'] ) && iCalUtilityFunctions::_isOffset( $inputa['tz'] )) {
3642             $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $inputa['year'], $inputa['month'], $inputa['day'], $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
3643             $inputa  = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
3644             unset( $inputa['unparsedtext'] );
3645           }
3646         }
3647       }
3648       elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18
3649         $inputa       = iCalUtilityFunctions::_strdate2date( $theRdate, $parno );
3650         unset( $inputa['unparsedtext'] );
3651         if( $toZ )
3652           $inputa['tz'] = 'Z';
3653       }
3654       if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
3655         if(( 0 == $rpix ) && !$toZ )
3656           $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
3657         if( $toZ )
3658           $inputa['tz']    = 'Z';
3659         if( 3 == $parno )
3660           unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
3661         elseif( isset( $inputa['tz'] ))
3662           $inputa['tz']    = (string) $inputa['tz'];
3663         if( isset( $input['params']['TZID'] ) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))))
3664           if( !$toZ )
3665             unset( $inputa['tz'] );
3666       }
3667       $input['value'][]    = $inputa;
3668     }
3669     if( 3 == $parno ) {
3670       $input['params']['VALUE'] = 'DATE';
3671       unset( $input['params']['TZID'] );
3672     }
3673     if( $toZ )
3674       unset( $input['params']['TZID'] );
3675     iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
3676     return TRUE;
3677   }
3678 /*********************************************************************************/
3679 /**
3680  * Property Name: RECURRENCE-ID
3681  */
3682 /**
3683  * creates formatted output for calendar component property recurrence-id
3684  *
3685  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3686  * @since 2.14.4 - 2012-09-26
3687  * @return string
3688  */
3689   function createRecurrenceid() {
3690     if( empty( $this->recurrenceid )) return FALSE;
3691     if( empty( $this->recurrenceid['value'] ))
3692       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
3693     $parno      = ( isset( $this->recurrenceid['params']['VALUE'] ) && ( 'DATE' == $this->recurrenceid['params']['VALUE'] )) ? 3 : null;
3694     $formatted  = iCalUtilityFunctions::_date2strdate( $this->recurrenceid['value'], $parno );
3695     $attributes = $this->_createParams( $this->recurrenceid['params'] );
3696     return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
3697   }
3698 /**
3699  * set calendar component property recurrence-id
3700  *
3701  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3702  * @since 2.9.6 - 2011-05-15
3703  * @param mixed $year
3704  * @param mixed $month optional
3705  * @param int $day optional
3706  * @param int $hour optional
3707  * @param int $min optional
3708  * @param int $sec optional
3709  * @param array $params optional
3710  * @return bool
3711  */
3712   function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
3713     if( empty( $year )) {
3714       if( $this->getConfig( 'allowEmpty' )) {
3715         $this->recurrenceid = array( 'value' => null, 'params' => null );
3716         return TRUE;
3717       }
3718       else
3719         return FALSE;
3720     }
3721     $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
3722     return TRUE;
3723   }
3724 /*********************************************************************************/
3725 /**
3726  * Property Name: RELATED-TO
3727  */
3728 /**
3729  * creates formatted output for calendar component property related-to
3730  *
3731  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3732  * @since 2.16.2 - 2012-12-18
3733  * @return string
3734  */
3735   function createRelatedTo() {
3736     if( empty( $this->relatedto )) return FALSE;
3737     $output = null;
3738     foreach( $this->relatedto as $relation ) {
3739       if( !empty( $relation['value'] ))
3740         $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), iCalUtilityFunctions::_strrep( $relation['value'], $this->format, $this->nl ));
3741       elseif( $this->getConfig( 'allowEmpty' ))
3742         $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
3743     }
3744     return $output;
3745   }
3746 /**
3747  * set calendar component property related-to
3748  *
3749  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3750  * @since 2.11.24 - 2012-02-23
3751  * @param float $relid
3752  * @param array $params, optional
3753  * @param index $index, optional
3754  * @return bool
3755  */
3756   function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
3757     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3758     iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
3759     iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
3760     return TRUE;
3761   }
3762 /*********************************************************************************/
3763 /**
3764  * Property Name: REPEAT
3765  */
3766 /**
3767  * creates formatted output for calendar component property repeat
3768  *
3769  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3770  * @since 2.9.3 - 2011-05-14
3771  * @return string
3772  */
3773   function createRepeat() {
3774     if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
3775     if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
3776       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
3777     $attributes = $this->_createParams( $this->repeat['params'] );
3778     return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
3779   }
3780 /**
3781  * set calendar component property repeat
3782  *
3783  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3784  * @since 2.9.3 - 2011-05-14
3785  * @param string $value
3786  * @param array $params optional
3787  * @return void
3788  */
3789   function setRepeat( $value, $params=FALSE ) {
3790     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3791     $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3792     return TRUE;
3793   }
3794 /*********************************************************************************/
3795 /**
3796  * Property Name: REQUEST-STATUS
3797  */
3798 /**
3799  * creates formatted output for calendar component property request-status
3800  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3801  * @since 2.16.2 - 2012-12-18
3802  * @return string
3803  */
3804   function createRequestStatus() {
3805     if( empty( $this->requeststatus )) return FALSE;
3806     $output = null;
3807     foreach( $this->requeststatus as $rstat ) {
3808       if( empty( $rstat['value']['statcode'] )) {
3809         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
3810         continue;
3811       }
3812       $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
3813       $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
3814       $content    .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['text'], $this->format, $this->nl );
3815       if( isset( $rstat['value']['extdata'] ))
3816         $content  .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['extdata'], $this->format, $this->nl );
3817       $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
3818     }
3819     return $output;
3820   }
3821 /**
3822  * set calendar component property request-status
3823  *
3824  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3825  * @since 2.5.1 - 2008-11-05
3826  * @param float $statcode
3827  * @param string $text
3828  * @param string $extdata, optional
3829  * @param array $params, optional
3830  * @param integer $index, optional
3831  * @return bool
3832  */
3833   function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
3834     if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = null; else return FALSE;
3835     $input              = array( 'statcode' => $statcode, 'text' => $text );
3836     if( $extdata )
3837       $input['extdata'] = $extdata;
3838     iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
3839     return TRUE;
3840   }
3841 /*********************************************************************************/
3842 /**
3843  * Property Name: RESOURCES
3844  */
3845 /**
3846  * creates formatted output for calendar component property resources
3847  *
3848  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3849  * @since 2.16.2 - 2012-12-18
3850  * @return string
3851  */
3852   function createResources() {
3853     if( empty( $this->resources )) return FALSE;
3854     $output = null;
3855     foreach( $this->resources as $resource ) {
3856       if( empty( $resource['value'] )) {
3857         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
3858         continue;
3859       }
3860       $attributes  = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
3861       if( is_array( $resource['value'] )) {
3862         foreach( $resource['value'] as $rix => $resourcePart )
3863           $resource['value'][$rix] = iCalUtilityFunctions::_strrep( $resourcePart, $this->format, $this->nl );
3864         $content   = implode( ',', $resource['value'] );
3865       }
3866       else
3867         $content   = iCalUtilityFunctions::_strrep( $resource['value'], $this->format, $this->nl );
3868       $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
3869     }
3870     return $output;
3871   }
3872 /**
3873  * set calendar component property recources
3874  *
3875  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3876  * @since 2.5.1 - 2008-11-05
3877  * @param mixed $value
3878  * @param array $params, optional
3879  * @param integer $index, optional
3880  * @return bool
3881  */
3882   function setResources( $value, $params=FALSE, $index=FALSE ) {
3883     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3884     iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
3885     return TRUE;
3886   }
3887 /*********************************************************************************/
3888 /**
3889  * Property Name: RRULE
3890  */
3891 /**
3892  * creates formatted output for calendar component property rrule
3893  *
3894  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3895  * @since 2.4.8 - 2008-10-21
3896  * @return string
3897  */
3898   function createRrule() {
3899     if( empty( $this->rrule )) return FALSE;
3900     return $this->_format_recur( 'RRULE', $this->rrule );
3901   }
3902 /**
3903  * set calendar component property rrule
3904  *
3905  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3906  * @since 2.5.1 - 2008-11-05
3907  * @param array $rruleset
3908  * @param array $params, optional
3909  * @param integer $index, optional
3910  * @return void
3911  */
3912   function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
3913     if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE;
3914     iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
3915     return TRUE;
3916   }
3917 /*********************************************************************************/
3918 /**
3919  * Property Name: SEQUENCE
3920  */
3921 /**
3922  * creates formatted output for calendar component property sequence
3923  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3924  * @since 2.9.3 - 2011-05-14
3925  * @return string
3926  */
3927   function createSequence() {
3928     if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
3929     if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
+3930        ( '0' != $this->sequence['value'] ))
3931       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
3932     $attributes = $this->_createParams( $this->sequence['params'] );
3933     return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
3934   }
3935 /**
3936  * set calendar component property sequence
3937  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3938  * @since 2.10.8 - 2011-09-19
3939  * @param int $value optional
3940  * @param array $params optional
3941  * @return bool
3942  */
3943   function setSequence( $value=FALSE, $params=FALSE ) {
3944     if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
3945       $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
3946     $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3947     return TRUE;
3948   }
3949 /*********************************************************************************/
3950 /**
3951  * Property Name: STATUS
3952  */
3953 /**
3954  * creates formatted output for calendar component property status
3955  *
3956  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3957  * @since 2.4.8 - 2008-10-21
3958  * @return string
3959  */
3960   function createStatus() {
3961     if( empty( $this->status )) return FALSE;
3962     if( empty( $this->status['value'] ))
3963       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
3964     $attributes = $this->_createParams( $this->status['params'] );
3965     return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
3966   }
3967 /**
3968  * set calendar component property status
3969  *
3970  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3971  * @since 2.4.8 - 2008-11-04
3972  * @param string $value
3973  * @param array $params optional
3974  * @return bool
3975  */
3976   function setStatus( $value, $params=FALSE ) {
3977     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3978     $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3979     return TRUE;
3980   }
3981 /*********************************************************************************/
3982 /**
3983  * Property Name: SUMMARY
3984  */
3985 /**
3986  * creates formatted output for calendar component property summary
3987  *
3988  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3989  * @since 2.16.2 - 2012-12-18
3990  * @return string
3991  */
3992   function createSummary() {
3993     if( empty( $this->summary )) return FALSE;
3994     if( empty( $this->summary['value'] ))
3995       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
3996     $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
3997     $content    = iCalUtilityFunctions::_strrep( $this->summary['value'], $this->format, $this->nl );
3998     return $this->_createElement( 'SUMMARY', $attributes, $content );
3999   }
4000 /**
4001  * set calendar component property summary
4002  *
4003  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4004  * @since 2.4.8 - 2008-11-04
4005  * @param string $value
4006  * @param string $params optional
4007  * @return bool
4008  */
4009   function setSummary( $value, $params=FALSE ) {
4010     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4011     $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4012     return TRUE;
4013   }
4014 /*********************************************************************************/
4015 /**
4016  * Property Name: TRANSP
4017  */
4018 /**
4019  * creates formatted output for calendar component property transp
4020  *
4021  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4022  * @since 2.4.8 - 2008-10-21
4023  * @return string
4024  */
4025   function createTransp() {
4026     if( empty( $this->transp )) return FALSE;
4027     if( empty( $this->transp['value'] ))
4028       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
4029     $attributes = $this->_createParams( $this->transp['params'] );
4030     return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
4031   }
4032 /**
4033  * set calendar component property transp
4034  *
4035  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4036  * @since 2.4.8 - 2008-11-04
4037  * @param string $value
4038  * @param string $params optional
4039  * @return bool
4040  */
4041   function setTransp( $value, $params=FALSE ) {
4042     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4043     $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4044     return TRUE;
4045   }
4046 /*********************************************************************************/
4047 /**
4048  * Property Name: TRIGGER
4049  */
4050 /**
4051  * creates formatted output for calendar component property trigger
4052  *
4053  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4054  * @since 2.4.16 - 2008-10-21
4055  * @return string
4056  */
4057   function createTrigger() {
4058     if( empty( $this->trigger )) return FALSE;
4059     if( empty( $this->trigger['value'] ))
4060       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
4061     $content = $attributes = null;
4062     if( isset( $this->trigger['value']['year'] )   &&
4063         isset( $this->trigger['value']['month'] )  &&
4064         isset( $this->trigger['value']['day'] ))
4065       $content      .= iCalUtilityFunctions::_date2strdate( $this->trigger['value'] );
4066     else {
4067       if( TRUE !== $this->trigger['value']['relatedStart'] )
4068         $attributes .= $this->intAttrDelimiter.'RELATED=END';
4069       if( $this->trigger['value']['before'] )
4070         $content    .= '-';
4071       $content      .= iCalUtilityFunctions::_duration2str( $this->trigger['value'] );
4072     }
4073     $attributes     .= $this->_createParams( $this->trigger['params'] );
4074     return $this->_createElement( 'TRIGGER', $attributes, $content );
4075   }
4076 /**
4077  * set calendar component property trigger
4078  *
4079  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4080  * @since 2.14.1 - 2012-09-20
4081  * @param mixed $year
4082  * @param mixed $month optional
4083  * @param int $day optional
4084  * @param int $week optional
4085  * @param int $hour optional
4086  * @param int $min optional
4087  * @param int $sec optional
4088  * @param bool $relatedStart optional
4089  * @param bool $before optional
4090  * @param array $params optional
4091  * @return bool
4092  */
4093   function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
4094     if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
4095       if( $this->getConfig( 'allowEmpty' )) {
4096         $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
4097         return TRUE;
4098       }
4099       else
4100         return FALSE;
4101     if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp UTC
4102       $params = iCalUtilityFunctions::_setParams( $month );
4103       $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 );
4104       foreach( $date as $k => $v )
4105         $$k = $v;
4106     }
4107     elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
4108       $params = iCalUtilityFunctions::_setParams( $month );
4109       if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
4110            array_key_exists( 'month', $year ) &&
+4111            array_key_exists( 'day',   $year ))) {  // when this must be a duration
4112         if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
4113           $relatedStart = FALSE;
4114         else
4115           $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
4116         $before         = ( array_key_exists( 'before', $year )       && ( TRUE !== $year['before'] ))       ? FALSE : TRUE;
4117       }
4118       $SSYY  = ( array_key_exists( 'year',  $year )) ? $year['year']  : null;
4119       $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
4120       $day   = ( array_key_exists( 'day',   $year )) ? $year['day']   : null;
4121       $week  = ( array_key_exists( 'week',  $year )) ? $year['week']  : null;
4122       $hour  = ( array_key_exists( 'hour',  $year )) ? $year['hour']  : 0; //null;
4123       $min   = ( array_key_exists( 'min',   $year )) ? $year['min']   : 0; //null;
4124       $sec   = ( array_key_exists( 'sec',   $year )) ? $year['sec']   : 0; //null;
4125       $year  = $SSYY;
4126     }
4127     elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
4128       $params = iCalUtilityFunctions::_setParams( $month );
4129       if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration
4130         $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
4131         $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
4132         if(     'P'  != $year[0] )
4133           $year       = substr( $year, 1 );
4134         $date         = iCalUtilityFunctions::_durationStr2arr( $year);
4135       }
4136       else   // date
4137         $date    = iCalUtilityFunctions::_strdate2date( $year, 7 );
4138       unset( $year, $month, $day, $date['unparsedtext'] );
4139       if( empty( $date ))
4140         $sec = 0;
4141       else
4142         foreach( $date as $k => $v )
4143           $$k = $v;
4144     }
4145     else // single values in function input parameters
4146       $params = iCalUtilityFunctions::_setParams( $params );
4147     if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
4148       $params['VALUE'] = 'DATE-TIME';
4149       $hour = ( $hour ) ? $hour : 0;
4150       $min  = ( $min  ) ? $min  : 0;
4151       $sec  = ( $sec  ) ? $sec  : 0;
4152       $this->trigger = array( 'params' => $params );
4153       $this->trigger['value'] = array( 'year'  => $year
+4154                                      , 'month' => $month
+4155                                      , 'day'   => $day
+4156                                      , 'hour'  => $hour
+4157                                      , 'min'   => $min
+4158                                      , 'sec'   => $sec
+4159                                      , 'tz'    => 'Z' );
4160       return TRUE;
4161     }
4162     elseif(( empty( $year ) && empty( $month )) &&    // duration
+4163            (( !empty( $week ) || ( 0 == $week )) ||
+4164             ( !empty( $day )  || ( 0 == $day  )) ||
+4165             ( !empty( $hour ) || ( 0 == $hour )) ||
+4166             ( !empty( $min )  || ( 0 == $min  )) ||
+4167             ( !empty( $sec )  || ( 0 == $sec  )))) {
4168       unset( $params['RELATED'] ); // set at output creation (END only)
4169       unset( $params['VALUE'] );   // 'DURATION' default
4170       $this->trigger = array( 'params' => $params );
4171       $this->trigger['value']  = array();
4172       if( !empty( $week )) $this->trigger['value']['week'] = $week;
4173       if( !empty( $day  )) $this->trigger['value']['day']  = $day;
4174       if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
4175       if( !empty( $min  )) $this->trigger['value']['min']  = $min;
4176       if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
4177       if( empty( $this->trigger['value'] )) {
4178         $this->trigger['value']['sec'] = 0;
4179         $before                        = FALSE;
4180       }
4181       $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
4182       $before       = ( FALSE !== $before )       ? TRUE : FALSE;
4183       $this->trigger['value']['relatedStart'] = $relatedStart;
4184       $this->trigger['value']['before']       = $before;
4185       return TRUE;
4186     }
4187     return FALSE;
4188   }
4189 /*********************************************************************************/
4190 /**
4191  * Property Name: TZID
4192  */
4193 /**
4194  * creates formatted output for calendar component property tzid
4195  *
4196  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4197  * @since 2.16.2 - 2012-12-18
4198  * @return string
4199  */
4200   function createTzid() {
4201     if( empty( $this->tzid )) return FALSE;
4202     if( empty( $this->tzid['value'] ))
4203       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
4204     $attributes = $this->_createParams( $this->tzid['params'] );
4205     return $this->_createElement( 'TZID', $attributes, iCalUtilityFunctions::_strrep( $this->tzid['value'], $this->format, $this->nl ));
4206   }
4207 /**
4208  * set calendar component property tzid
4209  *
4210  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4211  * @since 2.4.8 - 2008-11-04
4212  * @param string $value
4213  * @param array $params optional
4214  * @return bool
4215  */
4216   function setTzid( $value, $params=FALSE ) {
4217     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4218     $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4219     return TRUE;
4220   }
4221 /*********************************************************************************/
4222 /**
4223  * .. .
4224  * Property Name: TZNAME
4225  */
4226 /**
4227  * creates formatted output for calendar component property tzname
4228  *
4229  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4230  * @since 2.16.2 - 2012-12-18
4231  * @return string
4232  */
4233   function createTzname() {
4234     if( empty( $this->tzname )) return FALSE;
4235     $output = null;
4236     foreach( $this->tzname as $theName ) {
4237       if( !empty( $theName['value'] )) {
4238         $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
4239         $output    .= $this->_createElement( 'TZNAME', $attributes, iCalUtilityFunctions::_strrep( $theName['value'], $this->format, $this->nl ));
4240       }
4241       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
4242     }
4243     return $output;
4244   }
4245 /**
4246  * set calendar component property tzname
4247  *
4248  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4249  * @since 2.5.1 - 2008-11-05
4250  * @param string $value
4251  * @param string $params, optional
4252  * @param integer $index, optional
4253  * @return bool
4254  */
4255   function setTzname( $value, $params=FALSE, $index=FALSE ) {
4256     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4257     iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
4258     return TRUE;
4259   }
4260 /*********************************************************************************/
4261 /**
4262  * Property Name: TZOFFSETFROM
4263  */
4264 /**
4265  * creates formatted output for calendar component property tzoffsetfrom
4266  *
4267  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4268  * @since 2.4.8 - 2008-10-21
4269  * @return string
4270  */
4271   function createTzoffsetfrom() {
4272     if( empty( $this->tzoffsetfrom )) return FALSE;
4273     if( empty( $this->tzoffsetfrom['value'] ))
4274       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
4275     $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
4276     return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
4277   }
4278 /**
4279  * set calendar component property tzoffsetfrom
4280  *
4281  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4282  * @since 2.4.8 - 2008-11-04
4283  * @param string $value
4284  * @param string $params optional
4285  * @return bool
4286  */
4287   function setTzoffsetfrom( $value, $params=FALSE ) {
4288     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4289     $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4290     return TRUE;
4291   }
4292 /*********************************************************************************/
4293 /**
4294  * Property Name: TZOFFSETTO
4295  */
4296 /**
4297  * creates formatted output for calendar component property tzoffsetto
4298  *
4299  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4300  * @since 2.4.8 - 2008-10-21
4301  * @return string
4302  */
4303   function createTzoffsetto() {
4304     if( empty( $this->tzoffsetto )) return FALSE;
4305     if( empty( $this->tzoffsetto['value'] ))
4306       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
4307     $attributes = $this->_createParams( $this->tzoffsetto['params'] );
4308     return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
4309   }
4310 /**
4311  * set calendar component property tzoffsetto
4312  *
4313  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4314  * @since 2.4.8 - 2008-11-04
4315  * @param string $value
4316  * @param string $params optional
4317  * @return bool
4318  */
4319   function setTzoffsetto( $value, $params=FALSE ) {
4320     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4321     $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4322     return TRUE;
4323   }
4324 /*********************************************************************************/
4325 /**
4326  * Property Name: TZURL
4327  */
4328 /**
4329  * creates formatted output for calendar component property tzurl
4330  *
4331  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4332  * @since 2.4.8 - 2008-10-21
4333  * @return string
4334  */
4335   function createTzurl() {
4336     if( empty( $this->tzurl )) return FALSE;
4337     if( empty( $this->tzurl['value'] ))
4338       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
4339     $attributes = $this->_createParams( $this->tzurl['params'] );
4340     return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
4341   }
4342 /**
4343  * set calendar component property tzurl
4344  *
4345  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4346  * @since 2.4.8 - 2008-11-04
4347  * @param string $value
4348  * @param string $params optional
4349  * @return boll
4350  */
4351   function setTzurl( $value, $params=FALSE ) {
4352     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4353     $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4354     return TRUE;
4355   }
4356 /*********************************************************************************/
4357 /**
4358  * Property Name: UID
4359  */
4360 /**
4361  * creates formatted output for calendar component property uid
4362  *
4363  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4364  * @since 0.9.7 - 2006-11-20
4365  * @return string
4366  */
4367   function createUid() {
4368     if( 0 >= count( $this->uid ))
4369       $this->_makeuid();
4370     $attributes = $this->_createParams( $this->uid['params'] );
4371     return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
4372   }
4373 /**
4374  * create an unique id for this calendar component object instance
4375  *
4376  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4377  * @since 2.2.7 - 2007-09-04
4378  * @return void
4379  */
4380   function _makeUid() {
4381     $date   = date('Ymd\THisT');
4382     $unique = substr(microtime(), 2, 4);
4383     $base   = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
4384     $start  = 0;
4385     $end    = strlen( $base ) - 1;
4386     $length = 6;
4387     $str    = null;
4388     for( $p = 0; $p < $length; $p++ )
4389       $unique .= $base{mt_rand( $start, $end )};
4390     $this->uid = array( 'params' => null );
4391     $this->uid['value']  = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
4392   }
4393 /**
4394  * set calendar component property uid
4395  *
4396  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4397  * @since 2.4.8 - 2008-11-04
4398  * @param string $value
4399  * @param string $params optional
4400  * @return bool
4401  */
4402   function setUid( $value, $params=FALSE ) {
4403     if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
4404     $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4405     return TRUE;
4406   }
4407 /*********************************************************************************/
4408 /**
4409  * Property Name: URL
4410  */
4411 /**
4412  * creates formatted output for calendar component property url
4413  *
4414  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4415  * @since 2.4.8 - 2008-10-21
4416  * @return string
4417  */
4418   function createUrl() {
4419     if( empty( $this->url )) return FALSE;
4420     if( empty( $this->url['value'] ))
4421       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
4422     $attributes = $this->_createParams( $this->url['params'] );
4423     return $this->_createElement( 'URL', $attributes, $this->url['value'] );
4424   }
4425 /**
4426  * set calendar component property url
4427  *
4428  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4429  * @since 2.16.7 - 2013-01-11
4430  * @param string $value
4431  * @param string $params optional
4432  * @return bool
4433  */
4434   function setUrl( $value, $params=FALSE ) {
4435     if( !empty( $value )) {
4436       if( !filter_var( $value, FILTER_VALIDATE_URL ) && ( 'urn' != strtolower( substr( $value, 0, 3 ))))
4437         return FALSE;
4438     }
4439     elseif( $this->getConfig( 'allowEmpty' ))
4440       $value = null;
4441     else
4442       return FALSE;
4443     $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4444     return TRUE;
4445   }
4446 /*********************************************************************************/
4447 /**
4448  * Property Name: x-prop
4449  */
4450 /**
4451  * creates formatted output for calendar component property x-prop
4452  *
4453  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4454  * @since 2.16.2 - 2012-12-18
4455  * @return string
4456  */
4457   function createXprop() {
4458     if( empty( $this->xprop )) return FALSE;
4459     $output = null;
4460     foreach( $this->xprop as $label => $xpropPart ) {
4461       if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
4462         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
4463         continue;
4464       }
4465       $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
4466       if( is_array( $xpropPart['value'] )) {
4467         foreach( $xpropPart['value'] as $pix => $theXpart )
4468           $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->format );
4469         $xpropPart['value']  = implode( ',', $xpropPart['value'] );
4470       }
4471       else
4472         $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
4473       $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
4474     }
4475     return $output;
4476   }
4477 /**
4478  * set calendar component property x-prop
4479  *
4480  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4481  * @since 2.11.9 - 2012-01-16
4482  * @param string $label
4483  * @param mixed $value
4484  * @param array $params optional
4485  * @return bool
4486  */
4487   function setXprop( $label, $value, $params=FALSE ) {
4488     if( empty( $label ))
4489       return FALSE;
4490     if( 'X-' != strtoupper( substr( $label, 0, 2 )))
4491       return FALSE;
4492     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4493     $xprop           = array( 'value' => $value );
4494     $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
4495     if( !is_array( $this->xprop )) $this->xprop = array();
4496     $this->xprop[strtoupper( $label )] = $xprop;
4497     return TRUE;
4498   }
4499 /*********************************************************************************/
4500 /*********************************************************************************/
4501 /**
4502  * create element format parts
4503  *
4504  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4505  * @since 2.0.6 - 2006-06-20
4506  * @return string
4507  */
4508   function _createFormat() {
4509     $objectname                   = null;
4510     switch( $this->format ) {
4511       case 'xcal':
4512         $objectname               = ( isset( $this->timezonetype )) ?
4513                                  strtolower( $this->timezonetype )  :  strtolower( $this->objName );
4514         $this->componentStart1    = $this->elementStart1 = '<';
4515         $this->componentStart2    = $this->elementStart2 = '>';
4516         $this->componentEnd1      = $this->elementEnd1   = '</';
4517         $this->componentEnd2      = $this->elementEnd2   = '>'.$this->nl;
4518         $this->intAttrDelimiter   = '<!-- -->';
4519         $this->attributeDelimiter = $this->nl;
4520         $this->valueInit          = null;
4521         break;
4522       default:
4523         $objectname               = ( isset( $this->timezonetype )) ?
4524                                  strtoupper( $this->timezonetype )  :  strtoupper( $this->objName );
4525         $this->componentStart1    = 'BEGIN:';
4526         $this->componentStart2    = null;
4527         $this->componentEnd1      = 'END:';
4528         $this->componentEnd2      = $this->nl;
4529         $this->elementStart1      = null;
4530         $this->elementStart2      = null;
4531         $this->elementEnd1        = null;
4532         $this->elementEnd2        = $this->nl;
4533         $this->intAttrDelimiter   = '<!-- -->';
4534         $this->attributeDelimiter = ';';
4535         $this->valueInit          = ':';
4536         break;
4537     }
4538     return $objectname;
4539   }
4540 /**
4541  * creates formatted output for calendar component property
4542  *
4543  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4544  * @since 2.16.2 - 2012-12-18
4545  * @param string $label property name
4546  * @param string $attributes property attributes
4547  * @param string $content property content (optional)
4548  * @return string
4549  */
4550   function _createElement( $label, $attributes=null, $content=FALSE ) {
4551     switch( $this->format ) {
4552       case 'xcal':
4553         $label = strtolower( $label );
4554         break;
4555       default:
4556         $label = strtoupper( $label );
4557         break;
4558     }
4559     $output = $this->elementStart1.$label;
4560     $categoriesAttrLang = null;
4561     $attachInlineBinary = FALSE;
4562     $attachfmttype      = null;
4563     if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
4564       $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT'
+4565                                , 'ref'      => $label
+4566                                , 'type2'    => '(#PCDATA)' );
4567     }
4568     if( !empty( $attributes ))  {
4569       $attributes  = trim( $attributes );
4570       if ( 'xcal' == $this->format ) {
4571         $attributes2 = explode( $this->intAttrDelimiter, $attributes );
4572         $attributes  = null;
4573         foreach( $attributes2 as $aix => $attribute ) {
4574           $attrKVarr = explode( '=', $attribute );
4575           if( empty( $attrKVarr[0] ))
4576             continue;
4577           if( !isset( $attrKVarr[1] )) {
4578             $attrValue = $attrKVarr[0];
4579             $attrKey   = $aix;
4580           }
4581           elseif( 2 == count( $attrKVarr)) {
4582             $attrKey   = strtolower( $attrKVarr[0] );
4583             $attrValue = $attrKVarr[1];
4584           }
4585           else {
4586             $attrKey   = strtolower( $attrKVarr[0] );
4587             unset( $attrKVarr[0] );
4588             $attrValue = implode( '=', $attrKVarr );
4589           }
4590           if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
4591             $attachInlineBinary = TRUE;
4592             if( 'fmttype' == $attrKey )
4593               $attachfmttype = $attrKey.'='.$attrValue;
4594             continue;
4595           }
4596           elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
4597             $categoriesAttrLang = $attrKey.'='.$attrValue;
4598           else {
4599             $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
4600             $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
4601             if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
4602               $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
4603               $attrValue = str_replace( '"', '', $attrValue );
4604             }
4605             $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
4606           }
4607         }
4608       }
4609       else {
4610         $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
4611       }
4612     }
4613     if(( 'xcal' == $this->format) &&
+4614        ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
4615       $pos = strrpos($content, "/");
4616       $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
4617       $this->xcaldecl[] = array( 'xmldecl'  => 'ENTITY'
+4618                                , 'uri'      => $docname
+4619                                , 'ref'      => 'SYSTEM'
+4620                                , 'external' => $content
+4621                                , 'type'     => 'NDATA'
+4622                                , 'type2'    => 'BINERY' );
4623       $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
4624       $attributes .= 'uri="'.$docname.'"';
4625       $content = null;
4626       if( 'attach' == $label ) {
4627         $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
4628         $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
4629         $attributes = null;
4630       }
4631     }
4632     elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
4633       $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
4634     }
4635     $output .= $attributes;
4636     if( !$content && ( '0' != $content )) {
4637       switch( $this->format ) {
4638         case 'xcal':
4639           $output .= ' /';
4640           $output .= $this->elementStart2.$this->nl;
4641           return $output;
4642           break;
4643         default:
4644           $output .= $this->elementStart2.$this->valueInit;
4645           return iCalUtilityFunctions::_size75( $output, $this->nl );
4646           break;
4647       }
4648     }
4649     $output .= $this->elementStart2;
4650     $output .= $this->valueInit.$content;
4651     switch( $this->format ) {
4652       case 'xcal':
4653         return $output.$this->elementEnd1.$label.$this->elementEnd2;
4654         break;
4655       default:
4656         return iCalUtilityFunctions::_size75( $output, $this->nl );
4657         break;
4658     }
4659   }
4660 /**
4661  * creates formatted output for calendar component property parameters
4662  *
4663  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4664  * @since 2.10.27 - 2012-01-16
4665  * @param array $params  optional
4666  * @param array $ctrKeys optional
4667  * @return string
4668  */
4669   function _createParams( $params=array(), $ctrKeys=array() ) {
4670     if( !is_array( $params ) || empty( $params ))
4671       $params = array();
4672     $attrLANG = $attr1 = $attr2 = $lang = null;
4673     $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
4674     $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
4675     $CNattrExist = $LANGattrExist = FALSE;
4676     $xparams = array();
4677     foreach( $params as $paramKey => $paramValue ) {
4678       if(( FALSE !== strpos( $paramValue, ':' )) ||
+4679          ( FALSE !== strpos( $paramValue, ';' )) ||
+4680          ( FALSE !== strpos( $paramValue, ',' )))
4681         $paramValue = '"'.$paramValue.'"';
4682       if( ctype_digit( (string) $paramKey )) {
4683         $xparams[]          = $paramValue;
4684         continue;
4685       }
4686       $paramKey             = strtoupper( $paramKey );
4687       if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
4688         $xparams[$paramKey] = $paramValue;
4689       else
4690         $params[$paramKey]  = $paramValue;
4691     }
4692     ksort( $xparams, SORT_STRING );
4693     foreach( $xparams as $paramKey => $paramValue ) {
4694       if( ctype_digit( (string) $paramKey ))
4695         $attr2             .= $this->intAttrDelimiter.$paramValue;
4696       else
4697         $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
4698     }
4699     if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
4700       $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
4701       $attr2                = null;
4702     }
4703     if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
4704       if( !empty( $attr2 )) {
4705         $attr1             .= $attr2;
4706         $attr2              = null;
4707       }
4708       $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
4709     }
4710     if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
4711       $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
4712     if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) {
4713       $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
4714     }
4715     if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
4716       $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
4717     if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
4718       $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
4719     if( isset( $params['CN'] )       && $CNattrKey ) {
4720       $attr1                = $this->intAttrDelimiter.'CN='.$params['CN'];
4721       $CNattrExist          = TRUE;
4722     }
4723     if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) {
4724       $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
4725       $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
4726     }
4727     if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
4728       $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
4729     if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) {
4730       $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
4731       $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
4732     }
4733     if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
4734       $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
4735       $LANGattrExist        = TRUE;
4736     }
4737     if( !$LANGattrExist ) {
4738       $lang = $this->getConfig( 'language' );
4739       if(( $CNattrExist || $LANGattrKey ) && $lang )
4740         $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
4741     }
4742     return $attr1.$attrLANG.$attr2;
4743   }
4744 /**
4745  * creates formatted output for calendar component property data value type recur
4746  *
4747  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4748  * @since 2.14.1 - 2012-10-06
4749  * @param array $recurlabel
4750  * @param array $recurdata
4751  * @return string
4752  */
4753   function _format_recur( $recurlabel, $recurdata ) {
4754     $output = null;
4755     foreach( $recurdata as $therule ) {
4756       if( empty( $therule['value'] )) {
4757         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
4758         continue;
4759       }
4760       $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
4761       $content1  = $content2  = null;
4762       foreach( $therule['value'] as $rulelabel => $rulevalue ) {
4763         switch( $rulelabel ) {
4764           case 'FREQ': {
4765             $content1 .= "FREQ=$rulevalue";
4766             break;
4767           }
4768           case 'UNTIL': {
4769             $parno     = ( isset( $rulevalue['hour'] )) ? 7 : 3;
4770             $content2 .= ';UNTIL='.iCalUtilityFunctions::_date2strdate( $rulevalue, $parno );
4771             break;
4772           }
4773           case 'COUNT':
4774           case 'INTERVAL':
4775           case 'WKST': {
4776             $content2 .= ";$rulelabel=$rulevalue";
4777             break;
4778           }
4779           case 'BYSECOND':
4780           case 'BYMINUTE':
4781           case 'BYHOUR':
4782           case 'BYMONTHDAY':
4783           case 'BYYEARDAY':
4784           case 'BYWEEKNO':
4785           case 'BYMONTH':
4786           case 'BYSETPOS': {
4787             $content2 .= ";$rulelabel=";
4788             if( is_array( $rulevalue )) {
4789               foreach( $rulevalue as $vix => $valuePart ) {
4790                 $content2 .= ( $vix ) ? ',' : null;
4791                 $content2 .= $valuePart;
4792               }
4793             }
4794             else
4795              $content2 .= $rulevalue;
4796             break;
4797           }
4798           case 'BYDAY': {
4799             $content2 .= ";$rulelabel=";
4800             $bydaycnt = 0;
4801             foreach( $rulevalue as $vix => $valuePart ) {
4802               $content21 = $content22 = null;
4803               if( is_array( $valuePart )) {
4804                 $content2 .= ( $bydaycnt ) ? ',' : null;
4805                 foreach( $valuePart as $vix2 => $valuePart2 ) {
4806                   if( 'DAY' != strtoupper( $vix2 ))
4807                       $content21 .= $valuePart2;
4808                   else
4809                     $content22 .= $valuePart2;
4810                 }
4811                 $content2 .= $content21.$content22;
4812                 $bydaycnt++;
4813               }
4814               else {
4815                 $content2 .= ( $bydaycnt ) ? ',' : null;
4816                 if( 'DAY' != strtoupper( $vix ))
4817                     $content21 .= $valuePart;
4818                 else {
4819                   $content22 .= $valuePart;
4820                   $bydaycnt++;
4821                 }
4822                 $content2 .= $content21.$content22;
4823               }
4824             }
4825             break;
4826           }
4827           default: {
4828             $content2 .= ";$rulelabel=$rulevalue";
4829             break;
4830           }
4831         }
4832       }
4833       $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
4834     }
4835     return $output;
4836   }
4837 /**
4838  * check if property not exists within component
4839  *
4840  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4841  * @since 2.5.1 - 2008-10-15
4842  * @param string $propName
4843  * @return bool
4844  */
4845   function _notExistProp( $propName ) {
4846     if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
4847     $propName = strtolower( $propName );
4848     if(     'last-modified'    == $propName )  { if( !isset( $this->lastmodified ))    return TRUE; }
4849     elseif( 'percent-complete' == $propName )  { if( !isset( $this->percentcomplete )) return TRUE; }
4850     elseif( 'recurrence-id'    == $propName )  { if( !isset( $this->recurrenceid ))    return TRUE; }
4851     elseif( 'related-to'       == $propName )  { if( !isset( $this->relatedto ))       return TRUE; }
4852     elseif( 'request-status'   == $propName )  { if( !isset( $this->requeststatus ))   return TRUE; }
4853     elseif((       'x-' != substr($propName,0,2)) && !isset( $this->$propName ))       return TRUE;
4854     return FALSE;
4855   }
4856 /*********************************************************************************/
4857 /*********************************************************************************/
4858 /**
4859  * get general component config variables or info about subcomponents
4860  *
4861  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4862  * @since 2.9.6 - 2011-05-14
4863  * @param mixed $config
4864  * @return value
4865  */
4866   function getConfig( $config = FALSE) {
4867     if( !$config ) {
4868       $return = array();
4869       $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
4870       $return['FORMAT']      = $this->getConfig( 'FORMAT' );
4871       if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
4872         $return['LANGUAGE']  = $lang;
4873       $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
4874       $return['TZTD']        = $this->getConfig( 'TZID' );
4875       $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
4876       return $return;
4877     }
4878     switch( strtoupper( $config )) {
4879       case 'ALLOWEMPTY':
4880         return $this->allowEmpty;
4881         break;
4882       case 'COMPSINFO':
4883         unset( $this->compix );
4884         $info = array();
4885         if( isset( $this->components )) {
4886           foreach( $this->components as $cix => $component ) {
4887             if( empty( $component )) continue;
4888             $info[$cix]['ordno'] = $cix + 1;
4889             $info[$cix]['type']  = $component->objName;
4890             $info[$cix]['uid']   = $component->getProperty( 'uid' );
4891             $info[$cix]['props'] = $component->getConfig( 'propinfo' );
4892             $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
4893           }
4894         }
4895         return $info;
4896         break;
4897       case 'FORMAT':
4898         return $this->format;
4899         break;
4900       case 'LANGUAGE':
4901          // get language for calendar component as defined in [RFC 1766]
4902         return $this->language;
4903         break;
4904       case 'NL':
4905       case 'NEWLINECHAR':
4906         return $this->nl;
4907         break;
4908       case 'PROPINFO':
4909         $output = array();
4910         if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
4911           if( empty( $this->uid['value'] ))   $this->_makeuid();
4912                                               $output['UID']              = 1;
4913           if( empty( $this->dtstamp ))        $this->_makeDtstamp();
4914                                               $output['DTSTAMP']          = 1;
4915         }
4916         if( !empty( $this->summary ))         $output['SUMMARY']          = 1;
4917         if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description );
4918         if( !empty( $this->dtstart ))         $output['DTSTART']          = 1;
4919         if( !empty( $this->dtend ))           $output['DTEND']            = 1;
4920         if( !empty( $this->due ))             $output['DUE']              = 1;
4921         if( !empty( $this->duration ))        $output['DURATION']         = 1;
4922         if( !empty( $this->rrule ))           $output['RRULE']            = count( $this->rrule );
4923         if( !empty( $this->rdate ))           $output['RDATE']            = count( $this->rdate );
4924         if( !empty( $this->exdate ))          $output['EXDATE']           = count( $this->exdate );
4925         if( !empty( $this->exrule ))          $output['EXRULE']           = count( $this->exrule );
4926         if( !empty( $this->action ))          $output['ACTION']           = 1;
4927         if( !empty( $this->attach ))          $output['ATTACH']           = count( $this->attach );
4928         if( !empty( $this->attendee ))        $output['ATTENDEE']         = count( $this->attendee );
4929         if( !empty( $this->categories ))      $output['CATEGORIES']       = count( $this->categories );
4930         if( !empty( $this->class ))           $output['CLASS']            = 1;
4931         if( !empty( $this->comment ))         $output['COMMENT']          = count( $this->comment );
4932         if( !empty( $this->completed ))       $output['COMPLETED']        = 1;
4933         if( !empty( $this->contact ))         $output['CONTACT']          = count( $this->contact );
4934         if( !empty( $this->created ))         $output['CREATED']          = 1;
4935         if( !empty( $this->freebusy ))        $output['FREEBUSY']         = count( $this->freebusy );
4936         if( !empty( $this->geo ))             $output['GEO']              = 1;
4937         if( !empty( $this->lastmodified ))    $output['LAST-MODIFIED']    = 1;
4938         if( !empty( $this->location ))        $output['LOCATION']         = 1;
4939         if( !empty( $this->organizer ))       $output['ORGANIZER']        = 1;
4940         if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
4941         if( !empty( $this->priority ))        $output['PRIORITY']         = 1;
4942         if( !empty( $this->recurrenceid ))    $output['RECURRENCE-ID']    = 1;
4943         if( !empty( $this->relatedto ))       $output['RELATED-TO']       = count( $this->relatedto );
4944         if( !empty( $this->repeat ))          $output['REPEAT']           = 1;
4945         if( !empty( $this->requeststatus ))   $output['REQUEST-STATUS']   = count( $this->requeststatus );
4946         if( !empty( $this->resources ))       $output['RESOURCES']        = count( $this->resources );
4947         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
4948         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
4949         if( !empty( $this->status ))          $output['STATUS']           = 1;
4950         if( !empty( $this->transp ))          $output['TRANSP']           = 1;
4951         if( !empty( $this->trigger ))         $output['TRIGGER']          = 1;
4952         if( !empty( $this->tzid ))            $output['TZID']             = 1;
4953         if( !empty( $this->tzname ))          $output['TZNAME']           = count( $this->tzname );
4954         if( !empty( $this->tzoffsetfrom ))    $output['TZOFFSETFROM']     = 1;
4955         if( !empty( $this->tzoffsetto ))      $output['TZOFFSETTO']       = 1;
4956         if( !empty( $this->tzurl ))           $output['TZURL']            = 1;
4957         if( !empty( $this->url ))             $output['URL']              = 1;
4958         if( !empty( $this->xprop ))           $output['X-PROP']           = count( $this->xprop );
4959         return $output;
4960         break;
4961       case 'SETPROPERTYNAMES':
4962         return array_keys( $this->getConfig( 'propinfo' ));
4963         break;
4964       case 'TZID':
4965         return $this->dtzid;
4966         break;
4967       case 'UNIQUE_ID':
4968         if( empty( $this->unique_id ))
4969           $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
4970         return $this->unique_id;
4971         break;
4972     }
4973   }
4974 /**
4975  * general component config setting
4976  *
4977  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4978  * @since 2.10.18 - 2011-10-28
4979  * @param mixed  $config
4980  * @param string $value
4981  * @param bool   $softUpdate
4982  * @return void
4983  */
4984   function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
4985     if( is_array( $config )) {
4986       $ak = array_keys( $config );
4987       foreach( $ak as $k ) {
4988         if( 'NEWLINECHAR' == strtoupper( $k )) {
4989           if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
4990             return FALSE;
4991           unset( $config[$k] );
4992           break;
4993         }
4994       }
4995       foreach( $config as $cKey => $cValue ) {
4996         if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
4997           return FALSE;
4998       }
4999       return TRUE;
5000     }
5001     $res = FALSE;
5002     switch( strtoupper( $config )) {
5003       case 'ALLOWEMPTY':
5004         $this->allowEmpty = $value;
5005         $subcfg = array( 'ALLOWEMPTY' => $value );
5006         $res    = TRUE;
5007         break;
5008       case 'FORMAT':
5009         $value  = trim( strtolower( $value ));
5010         $this->format = $value;
5011         $this->_createFormat();
5012         $subcfg = array( 'FORMAT' => $value );
5013         $res    = TRUE;
5014         break;
5015       case 'LANGUAGE':
5016          // set language for calendar component as defined in [RFC 1766]
5017         $value  = trim( $value );
5018         if( empty( $this->language ) || !$softUpdate )
5019           $this->language = $value;
5020         $subcfg = array( 'LANGUAGE' => $value );
5021         $res    = TRUE;
5022         break;
5023       case 'NL':
5024       case 'NEWLINECHAR':
5025         $this->nl = $value;
5026         $this->_createFormat();
5027         $subcfg = array( 'NL' => $value );
5028         $res    = TRUE;
5029         break;
5030       case 'TZID':
5031         $this->dtzid = $value;
5032         $subcfg = array( 'TZID' => $value );
5033         $res    = TRUE;
5034         break;
5035       case 'UNIQUE_ID':
5036         $value  = trim( $value );
5037         $this->unique_id = $value;
5038         $subcfg = array( 'UNIQUE_ID' => $value );
5039         $res    = TRUE;
5040         break;
5041       default:  // any unvalid config key.. .
5042         return TRUE;
5043     }
5044     if( !$res ) return FALSE;
5045     if( isset( $subcfg ) && !empty( $this->components )) {
5046       foreach( $subcfg as $cfgkey => $cfgvalue ) {
5047         foreach( $this->components as $cix => $component ) {
5048           $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
5049           if( !$res )
5050             break 2;
5051           $this->components[$cix] = $component->copy(); // PHP4 compliant
5052         }
5053       }
5054     }
5055     return $res;
5056   }
5057 /*********************************************************************************/
5058 /**
5059  * delete component property value
5060  *
5061  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5062  * @since 2.8.8 - 2011-03-15
5063  * @param mixed $propName, bool FALSE => X-property
5064  * @param int   $propix, optional, if specific property is wanted in case of multiply occurences
5065  * @return bool, if successfull delete TRUE
5066  */
5067   function deleteProperty( $propName=FALSE, $propix=FALSE ) {
5068     if( $this->_notExistProp( $propName )) return FALSE;
5069     $propName = strtoupper( $propName );
5070     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
+5071                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
5072       if( !$propix )
5073         $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
5074       $this->propdelix[$propName] = --$propix;
5075     }
5076     $return = FALSE;
5077     switch( $propName ) {
5078       case 'ACTION':
5079         if( !empty( $this->action )) {
5080           $this->action = '';
5081           $return = TRUE;
5082         }
5083         break;
5084       case 'ATTACH':
5085         return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
5086         break;
5087       case 'ATTENDEE':
5088         return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
5089         break;
5090       case 'CATEGORIES':
5091         return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
5092         break;
5093       case 'CLASS':
5094         if( !empty( $this->class )) {
5095           $this->class = '';
5096           $return = TRUE;
5097         }
5098         break;
5099       case 'COMMENT':
5100         return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
5101         break;
5102       case 'COMPLETED':
5103         if( !empty( $this->completed )) {
5104           $this->completed = '';
5105           $return = TRUE;
5106         }
5107         break;
5108       case 'CONTACT':
5109         return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
5110         break;
5111       case 'CREATED':
5112         if( !empty( $this->created )) {
5113           $this->created = '';
5114           $return = TRUE;
5115         }
5116         break;
5117       case 'DESCRIPTION':
5118         return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
5119         break;
5120       case 'DTEND':
5121         if( !empty( $this->dtend )) {
5122           $this->dtend = '';
5123           $return = TRUE;
5124         }
5125         break;
5126       case 'DTSTAMP':
5127         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5128           return FALSE;
5129         if( !empty( $this->dtstamp )) {
5130           $this->dtstamp = '';
5131           $return = TRUE;
5132         }
5133         break;
5134       case 'DTSTART':
5135         if( !empty( $this->dtstart )) {
5136           $this->dtstart = '';
5137           $return = TRUE;
5138         }
5139         break;
5140       case 'DUE':
5141         if( !empty( $this->due )) {
5142           $this->due = '';
5143           $return = TRUE;
5144         }
5145         break;
5146       case 'DURATION':
5147         if( !empty( $this->duration )) {
5148           $this->duration = '';
5149           $return = TRUE;
5150         }
5151         break;
5152       case 'EXDATE':
5153         return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
5154         break;
5155       case 'EXRULE':
5156         return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
5157         break;
5158       case 'FREEBUSY':
5159         return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
5160         break;
5161       case 'GEO':
5162         if( !empty( $this->geo )) {
5163           $this->geo = '';
5164           $return = TRUE;
5165         }
5166         break;
5167       case 'LAST-MODIFIED':
5168         if( !empty( $this->lastmodified )) {
5169           $this->lastmodified = '';
5170           $return = TRUE;
5171         }
5172         break;
5173       case 'LOCATION':
5174         if( !empty( $this->location )) {
5175           $this->location = '';
5176           $return = TRUE;
5177         }
5178         break;
5179       case 'ORGANIZER':
5180         if( !empty( $this->organizer )) {
5181           $this->organizer = '';
5182           $return = TRUE;
5183         }
5184         break;
5185       case 'PERCENT-COMPLETE':
5186         if( !empty( $this->percentcomplete )) {
5187           $this->percentcomplete = '';
5188           $return = TRUE;
5189         }
5190         break;
5191       case 'PRIORITY':
5192         if( !empty( $this->priority )) {
5193           $this->priority = '';
5194           $return = TRUE;
5195         }
5196         break;
5197       case 'RDATE':
5198         return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
5199         break;
5200       case 'RECURRENCE-ID':
5201         if( !empty( $this->recurrenceid )) {
5202           $this->recurrenceid = '';
5203           $return = TRUE;
5204         }
5205         break;
5206       case 'RELATED-TO':
5207         return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
5208         break;
5209       case 'REPEAT':
5210         if( !empty( $this->repeat )) {
5211           $this->repeat = '';
5212           $return = TRUE;
5213         }
5214         break;
5215       case 'REQUEST-STATUS':
5216         return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
5217         break;
5218       case 'RESOURCES':
5219         return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
5220         break;
5221       case 'RRULE':
5222         return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
5223         break;
5224       case 'SEQUENCE':
5225         if( !empty( $this->sequence )) {
5226           $this->sequence = '';
5227           $return = TRUE;
5228         }
5229         break;
5230       case 'STATUS':
5231         if( !empty( $this->status )) {
5232           $this->status = '';
5233           $return = TRUE;
5234         }
5235         break;
5236       case 'SUMMARY':
5237         if( !empty( $this->summary )) {
5238           $this->summary = '';
5239           $return = TRUE;
5240         }
5241         break;
5242       case 'TRANSP':
5243         if( !empty( $this->transp )) {
5244           $this->transp = '';
5245           $return = TRUE;
5246         }
5247         break;
5248       case 'TRIGGER':
5249         if( !empty( $this->trigger )) {
5250           $this->trigger = '';
5251           $return = TRUE;
5252         }
5253         break;
5254       case 'TZID':
5255         if( !empty( $this->tzid )) {
5256           $this->tzid = '';
5257           $return = TRUE;
5258         }
5259         break;
5260       case 'TZNAME':
5261         return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
5262         break;
5263       case 'TZOFFSETFROM':
5264         if( !empty( $this->tzoffsetfrom )) {
5265           $this->tzoffsetfrom = '';
5266           $return = TRUE;
5267         }
5268         break;
5269       case 'TZOFFSETTO':
5270         if( !empty( $this->tzoffsetto )) {
5271           $this->tzoffsetto = '';
5272           $return = TRUE;
5273         }
5274         break;
5275       case 'TZURL':
5276         if( !empty( $this->tzurl )) {
5277           $this->tzurl = '';
5278           $return = TRUE;
5279         }
5280         break;
5281       case 'UID':
5282         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5283           return FALSE;
5284         if( !empty( $this->uid )) {
5285           $this->uid = '';
5286           $return = TRUE;
5287         }
5288         break;
5289       case 'URL':
5290         if( !empty( $this->url )) {
5291           $this->url = '';
5292           $return = TRUE;
5293         }
5294         break;
5295       default:
5296         $reduced = '';
5297         if( $propName != 'X-PROP' ) {
5298           if( !isset( $this->xprop[$propName] )) return FALSE;
5299           foreach( $this->xprop as $k => $a ) {
5300             if(( $k != $propName ) && !empty( $a ))
5301               $reduced[$k] = $a;
5302           }
5303         }
5304         else {
5305           if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
5306           $xpropno = 0;
5307           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
5308             if( $propix != $xpropno )
5309               $reduced[$xpropkey] = $xpropvalue;
5310             $xpropno++;
5311           }
5312         }
5313         $this->xprop = $reduced;
5314         if( empty( $this->xprop )) {
5315           unset( $this->propdelix[$propName] );
5316           return FALSE;
5317         }
5318         return TRUE;
5319     }
5320     return $return;
5321   }
5322 /*********************************************************************************/
5323 /**
5324  * delete component property value, fixing components with multiple occurencies
5325  *
5326  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5327  * @since 2.8.8 - 2011-03-15
5328  * @param array $multiprop, reference to a component property
5329  * @param int   $propix, reference to removal counter
5330  * @return bool TRUE
5331  */
5332   function deletePropertyM( & $multiprop, & $propix ) {
5333     if( isset( $multiprop[$propix] ))
5334       unset( $multiprop[$propix] );
5335     if( empty( $multiprop )) {
5336       $multiprop = '';
5337       unset( $propix );
5338       return FALSE;
5339     }
5340     else
5341       return TRUE;
5342   }
5343 /**
5344  * get component property value/params
5345  *
5346  * if property has multiply values, consequtive function calls are needed
5347  *
5348  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5349  * @since 2.12.4 - 2012-04-22
5350  * @param string $propName, optional
5351  * @param int @propix, optional, if specific property is wanted in case of multiply occurences
5352  * @param bool $inclParam=FALSE
5353  * @param bool $specform=FALSE
5354  * @return mixed
5355  */
5356   function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
5357     if( 'GEOLOCATION' == strtoupper( $propName )) {
5358       $content = $this->getProperty( 'LOCATION' );
5359       $content = ( !empty( $content )) ? $content.' ' : '';
5360       if(( FALSE === ( $geo     = $this->getProperty( 'GEO' ))) || empty( $geo ))
5361         return FALSE;
5362       if( 0.0 < $geo['latitude'] )
5363         $sign   = '+';
5364       else
5365         $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
5366       $content .= $sign.sprintf( "%09.6f", abs( $geo['latitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
5367       $content  = rtrim( rtrim( $content, '0' ), '.' );
5368       if( 0.0 < $geo['longitude'] )
5369         $sign   = '+';
5370       else
5371        $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
5372       return $content.$sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';   // sprintf && lpad && float && sign !"#¤%&/(
5373     }
5374     if( $this->_notExistProp( $propName )) return FALSE;
5375     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
5376     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
+5377                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
5378       if( !$propix )
5379         $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
5380       $this->propix[$propName] = --$propix;
5381     }
5382     switch( $propName ) {
5383       case 'ACTION':
5384         if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
5385         break;
5386       case 'ATTACH':
5387         $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
5388         while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
5389           $propix++;
5390         $this->propix[$propName] = $propix;
5391         if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5392         return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
5393         break;
5394       case 'ATTENDEE':
5395         $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
5396         while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
5397           $propix++;
5398         $this->propix[$propName] = $propix;
5399         if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5400         return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
5401         break;
5402       case 'CATEGORIES':
5403         $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
5404         while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
5405           $propix++;
5406         $this->propix[$propName] = $propix;
5407         if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5408         return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
5409         break;
5410       case 'CLASS':
5411         if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
5412         break;
5413       case 'COMMENT':
5414         $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
5415         while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
5416           $propix++;
5417         $this->propix[$propName] = $propix;
5418         if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5419         return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
5420         break;
5421       case 'COMPLETED':
5422         if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
5423         break;
5424       case 'CONTACT':
5425         $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
5426         while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
5427           $propix++;
5428         $this->propix[$propName] = $propix;
5429         if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5430         return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
5431         break;
5432       case 'CREATED':
5433         if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
5434         break;
5435       case 'DESCRIPTION':
5436         $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
5437         while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
5438           $propix++;
5439         $this->propix[$propName] = $propix;
5440         if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5441         return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
5442         break;
5443       case 'DTEND':
5444         if( !empty( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
5445         break;
5446       case 'DTSTAMP':
5447         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5448           return;
5449         if( !isset( $this->dtstamp['value'] ))
5450           $this->_makeDtstamp();
5451         return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
5452         break;
5453       case 'DTSTART':
5454         if( !empty( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
5455         break;
5456       case 'DUE':
5457         if( !empty( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
5458         break;
5459       case 'DURATION':
5460         if( !isset( $this->duration['value'] )) return FALSE;
5461         $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
5462         return ( $inclParam ) ? array( 'value' => $value, 'params' =>  $this->duration['params'] ) : $value;
5463         break;
5464       case 'EXDATE':
5465         $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
5466         while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
5467           $propix++;
5468         $this->propix[$propName] = $propix;
5469         if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5470         return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
5471         break;
5472       case 'EXRULE':
5473         $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
5474         while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
5475           $propix++;
5476         $this->propix[$propName] = $propix;
5477         if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5478         return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
5479         break;
5480       case 'FREEBUSY':
5481         $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
5482         while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
5483           $propix++;
5484         $this->propix[$propName] = $propix;
5485         if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5486         return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
5487         break;
5488       case 'GEO':
5489         if( !empty( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
5490         break;
5491       case 'LAST-MODIFIED':
5492         if( !empty( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
5493         break;
5494       case 'LOCATION':
5495         if( !empty( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
5496         break;
5497       case 'ORGANIZER':
5498         if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
5499         break;
5500       case 'PERCENT-COMPLETE':
5501         if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
5502         break;
5503       case 'PRIORITY':
5504         if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value'];
5505         break;
5506       case 'RDATE':
5507         $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
5508         while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
5509           $propix++;
5510         $this->propix[$propName] = $propix;
5511         if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5512         return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
5513         break;
5514       case 'RECURRENCE-ID':
5515         if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
5516         break;
5517       case 'RELATED-TO':
5518         $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
5519         while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
5520           $propix++;
5521         $this->propix[$propName] = $propix;
5522         if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5523         return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
5524         break;
5525       case 'REPEAT':
5526         if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
5527         break;
5528       case 'REQUEST-STATUS':
5529         $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
5530         while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
5531           $propix++;
5532         $this->propix[$propName] = $propix;
5533         if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5534         return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
5535         break;
5536       case 'RESOURCES':
5537         $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
5538         while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
5539           $propix++;
5540         $this->propix[$propName] = $propix;
5541         if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5542         return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
5543         break;
5544       case 'RRULE':
5545         $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
5546         while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
5547           $propix++;
5548         $this->propix[$propName] = $propix;
5549         if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5550         return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
5551         break;
5552       case 'SEQUENCE':
5553         if( isset( $this->sequence['value'] ) && ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))) return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
5554         break;
5555       case 'STATUS':
5556         if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
5557         break;
5558       case 'SUMMARY':
5559         if( !empty( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
5560         break;
5561       case 'TRANSP':
5562         if( !empty( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
5563         break;
5564       case 'TRIGGER':
5565         if( !empty( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
5566         break;
5567       case 'TZID':
5568         if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
5569         break;
5570       case 'TZNAME':
5571         $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
5572         while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
5573           $propix++;
5574         $this->propix[$propName] = $propix;
5575         if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5576         return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
5577         break;
5578       case 'TZOFFSETFROM':
5579         if( !empty( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
5580         break;
5581       case 'TZOFFSETTO':
5582         if( !empty( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
5583         break;
5584       case 'TZURL':
5585         if( !empty( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
5586         break;
5587       case 'UID':
5588         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5589           return FALSE;
5590         if( empty( $this->uid['value'] ))
5591           $this->_makeuid();
5592         return ( $inclParam ) ? $this->uid : $this->uid['value'];
5593         break;
5594       case 'URL':
5595         if( !empty( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
5596         break;
5597       default:
5598         if( $propName != 'X-PROP' ) {
5599           if( !isset( $this->xprop[$propName] )) return FALSE;
5600           return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
5601                                 : array( $propName, $this->xprop[$propName]['value'] );
5602         }
5603         else {
5604           if( empty( $this->xprop )) return FALSE;
5605           $xpropno = 0;
5606           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
5607             if( $propix == $xpropno )
5608               return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
5609                                     : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
5610             else
5611               $xpropno++;
5612           }
5613           return FALSE; // not found ??
5614         }
5615     }
5616     return FALSE;
5617   }
5618 /**
5619  * returns calendar property unique values for 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO' or 'RESOURCES' and for each, number of occurrence
5620  *
5621  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5622  * @since 2.13.4 - 2012-08-07
5623  * @param string $propName
5624  * @param array  $output, incremented result array
5625  */
5626   function _getProperties( $propName, & $output ) {
5627     if( empty( $output ))
5628       $output = array();
5629     if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' )))
5630       return $output;
5631     while( FALSE !== ( $content = $this->getProperty( $propName ))) {
5632       if( empty( $content ))
5633         continue;
5634       if( is_array( $content )) {
5635         foreach( $content as $part ) {
5636           if( FALSE !== strpos( $part, ',' )) {
5637             $part = explode( ',', $part );
5638             foreach( $part as $thePart ) {
5639               $thePart = trim( $thePart );
5640               if( !empty( $thePart )) {
5641                 if( !isset( $output[$thePart] ))
5642                   $output[$thePart] = 1;
5643                 else
5644                   $output[$thePart] += 1;
5645               }
5646             }
5647           }
5648           else {
5649             $part = trim( $part );
5650             if( !isset( $output[$part] ))
5651               $output[$part] = 1;
5652             else
5653               $output[$part] += 1;
5654           }
5655         }
5656       } // end if( is_array( $content ))
5657       elseif( FALSE !== strpos( $content, ',' )) {
5658         $content = explode( ',', $content );
5659         foreach( $content as $thePart ) {
5660           $thePart = trim( $thePart );
5661           if( !empty( $thePart )) {
5662             if( !isset( $output[$thePart] ))
5663               $output[$thePart] = 1;
5664             else
5665               $output[$thePart] += 1;
5666           }
5667         }
5668       } // end elseif( FALSE !== strpos( $content, ',' ))
5669       else {
5670         $content = trim( $content );
5671         if( !empty( $content )) {
5672           if( !isset( $output[$content] ))
5673             $output[$content] = 1;
5674           else
5675             $output[$content] += 1;
5676         }
5677       }
5678     }
5679     ksort( $output );
5680   }
5681 /**
5682  * general component property setting
5683  *
5684  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5685  * @since 2.5.1 - 2008-11-05
5686  * @param mixed $args variable number of function arguments,
5687  *                    first argument is ALWAYS component name,
5688  *                    second ALWAYS component value!
5689  * @return void
5690  */
5691   function setProperty() {
5692     $numargs    = func_num_args();
5693     if( 1 > $numargs ) return FALSE;
5694     $arglist    = func_get_args();
5695     if( $this->_notExistProp( $arglist[0] )) return FALSE;
5696     if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
5697       return FALSE;
5698     $arglist[0] = strtoupper( $arglist[0] );
5699     for( $argix=$numargs; $argix < 12; $argix++ ) {
5700       if( !isset( $arglist[$argix] ))
5701         $arglist[$argix] = null;
5702     }
5703     switch( $arglist[0] ) {
5704       case 'ACTION':
5705         return $this->setAction( $arglist[1], $arglist[2] );
5706       case 'ATTACH':
5707         return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] );
5708       case 'ATTENDEE':
5709         return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] );
5710       case 'CATEGORIES':
5711         return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] );
5712       case 'CLASS':
5713         return $this->setClass( $arglist[1], $arglist[2] );
5714       case 'COMMENT':
5715         return $this->setComment( $arglist[1], $arglist[2], $arglist[3] );
5716       case 'COMPLETED':
5717         return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5718       case 'CONTACT':
5719         return $this->setContact( $arglist[1], $arglist[2], $arglist[3] );
5720       case 'CREATED':
5721         return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5722       case 'DESCRIPTION':
5723         return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] );
5724       case 'DTEND':
5725         return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5726       case 'DTSTAMP':
5727         return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5728       case 'DTSTART':
5729         return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5730       case 'DUE':
5731         return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5732       case 'DURATION':
5733         return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
5734       case 'EXDATE':
5735         return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] );
5736       case 'EXRULE':
5737         return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] );
5738       case 'FREEBUSY':
5739         return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
5740       case 'GEO':
5741         return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] );
5742       case 'LAST-MODIFIED':
5743         return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5744       case 'LOCATION':
5745         return $this->setLocation( $arglist[1], $arglist[2] );
5746       case 'ORGANIZER':
5747         return $this->setOrganizer( $arglist[1], $arglist[2] );
5748       case 'PERCENT-COMPLETE':
5749         return $this->setPercentComplete( $arglist[1], $arglist[2] );
5750       case 'PRIORITY':
5751         return $this->setPriority( $arglist[1], $arglist[2] );
5752       case 'RDATE':
5753         return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] );
5754       case 'RECURRENCE-ID':
5755        return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5756       case 'RELATED-TO':
5757         return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] );
5758       case 'REPEAT':
5759         return $this->setRepeat( $arglist[1], $arglist[2] );
5760       case 'REQUEST-STATUS':
5761         return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
5762       case 'RESOURCES':
5763         return $this->setResources( $arglist[1], $arglist[2], $arglist[3] );
5764       case 'RRULE':
5765         return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] );
5766       case 'SEQUENCE':
5767         return $this->setSequence( $arglist[1], $arglist[2] );
5768       case 'STATUS':
5769         return $this->setStatus( $arglist[1], $arglist[2] );
5770       case 'SUMMARY':
5771         return $this->setSummary( $arglist[1], $arglist[2] );
5772       case 'TRANSP':
5773         return $this->setTransp( $arglist[1], $arglist[2] );
5774       case 'TRIGGER':
5775         return $this->setTrigger( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8], $arglist[9], $arglist[10], $arglist[11] );
5776       case 'TZID':
5777         return $this->setTzid( $arglist[1], $arglist[2] );
5778       case 'TZNAME':
5779         return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] );
5780       case 'TZOFFSETFROM':
5781         return $this->setTzoffsetfrom( $arglist[1], $arglist[2] );
5782       case 'TZOFFSETTO':
5783         return $this->setTzoffsetto( $arglist[1], $arglist[2] );
5784       case 'TZURL':
5785         return $this->setTzurl( $arglist[1], $arglist[2] );
5786       case 'UID':
5787         return $this->setUid( $arglist[1], $arglist[2] );
5788       case 'URL':
5789         return $this->setUrl( $arglist[1], $arglist[2] );
5790       default:
5791         return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
5792     }
5793     return FALSE;
5794   }
5795 /*********************************************************************************/
5796 /**
5797  * parse component unparsed data into properties
5798  *
5799  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5800  * @since 2.16.2 - 2012-12-18
5801  * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
5802  * @return bool FALSE if error occurs during parsing
5803  *
5804  */
5805   function parse( $unparsedtext=null ) {
5806     $nl = $this->getConfig( 'nl' );
5807     if( !empty( $unparsedtext )) {
5808       if( is_array( $unparsedtext ))
5809         $unparsedtext = implode( '\n'.$nl, $unparsedtext );
5810       $unparsedtext = explode( $nl, iCalUtilityFunctions::convEolChar( $unparsedtext, $nl ));
5811     }
5812     elseif( !isset( $this->unparsed ))
5813       $unparsedtext = array();
5814     else
5815       $unparsedtext = $this->unparsed;
5816             /* skip leading (empty/invalid) lines */
5817     foreach( $unparsedtext as $lix => $line ) {
5818       $tst = trim( $line );
5819       if(( '\n' == $tst ) || empty( $tst ))
5820         unset( $unparsedtext[$lix] );
5821       else
5822         break;
5823     }
5824     $this->unparsed = array();
5825     $comp           = & $this;
5826     $config         = $this->getConfig();
5827     $compsync = $subsync = 0;
5828     foreach ( $unparsedtext as $lix => $line ) {
5829       if( 'END:VALARM'         == strtoupper( substr( $line, 0, 10 ))) {
5830         if( 1 != $subsync ) return FALSE;
5831         $this->components[]     = $comp->copy();
5832         $subsync--;
5833       }
5834       elseif( 'END:DAYLIGHT'   == strtoupper( substr( $line, 0, 12 ))) {
5835         if( 1 != $subsync ) return FALSE;
5836         $this->components[]     = $comp->copy();
5837         $subsync--;
5838       }
5839       elseif( 'END:STANDARD'   == strtoupper( substr( $line, 0, 12 ))) {
5840         if( 1 != $subsync ) return FALSE;
5841         array_unshift( $this->components, $comp->copy());
5842         $subsync--;
5843       }
5844       elseif( 'END:'           == strtoupper( substr( $line, 0, 4 ))) { // end:<component>
5845         if( 1 != $compsync ) return FALSE;
5846         if( 0 < $subsync )
5847           $this->components[]   = $comp->copy();
5848         $compsync--;
5849         break;                       /* skip trailing empty lines */
5850       }
5851       elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 ))) {
5852         $comp = new valarm( $config);
5853         $subsync++;
5854       }
5855       elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) {
5856         $comp = new vtimezone( 'standard', $config );
5857         $subsync++;
5858       }
5859       elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) {
5860         $comp = new vtimezone( 'daylight', $config );
5861         $subsync++;
5862       }
5863       elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))  // begin:<component>
5864         $compsync++;
5865       else
5866         $comp->unparsed[]       = $line;
5867     }
5868     if( 0 < $subsync )
5869       $this->components[]   = $comp->copy();
5870     unset( $config );
5871             /* concatenate property values spread over several lines */
5872     $lastix    = -1;
5873     $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
+5874                       , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
+5875                       , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
+5876                       , 'last-modified', 'location', 'organizer', 'percent-complete'
+5877                       , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
+5878                       , 'request-status', 'resources', 'rrule', 'sequence', 'status'
+5879                       , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
+5880                       , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
5881     $proprows  = array();
5882     for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
5883       $line = rtrim( $this->unparsed[$i], $nl );
5884       while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
5885         $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
5886       $proprows[] = $line;
5887     }
5888             /* parse each property 'line' */
5889     $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
5890     $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
5891     $paramProto4 = array( 'crid:', 'news:', 'pres:' );
5892     foreach( $proprows as $line ) {
5893       if( '\n' == substr( $line, -2 ))
5894         $line = substr( $line, 0, -2 );
5895             /* get propname */
5896       $propname = null;
5897       $cix = 0;
5898       while( isset( $line[$cix] )) {
5899         if( in_array( $line[$cix], array( ':', ';' )))
5900           break;
5901         else
5902           $propname .= $line[$cix];
5903         $cix++;
5904       }
5905       if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
5906         $propname2 = $propname;
5907         $propname  = 'X-';
5908       }
5909       if( !in_array( strtolower( $propname ), $propnames )) // skip non standard property names
5910         continue;
5911             /* rest of the line is opt.params and value */
5912       $line = substr( $line, $cix );
5913             /* separate attributes from value */
5914       $attr         = array();
5915       $attrix       = -1;
5916       $clen         = strlen( $line );
5917       $WithinQuotes = FALSE;
5918       $cix          = 0;
5919       while( FALSE !== substr( $line, $cix, 1 )) {
5920         if(                       (  ':' == $line[$cix] )                         &&
+5921                                   ( substr( $line,$cix,     3 )  != '://' )       &&
+5922            ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
+5923            ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
+5924            ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
+5925                       ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
5926              !$WithinQuotes ) {
5927           $attrEnd = TRUE;
5928           if(( $cix < ( $clen - 4 )) &&
+5929                ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
5930             for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
5931               if( '://' == substr( $line, $c2ix - 2, 3 )) {
5932                 $attrEnd = FALSE;
5933                 break; // an URI with a portnr!!
5934               }
5935             }
5936           }
5937           if( $attrEnd) {
5938             $line = substr( $line, ( $cix + 1 ));
5939             break;
5940           }
5941           $cix++;
5942         }
5943         if( '"' == $line[$cix] )
5944           $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
5945         if( ';' == $line[$cix] )
5946           $attr[++$attrix] = null;
5947         else
5948           $attr[$attrix] .= $line[$cix];
5949         $cix++;
5950       }
5951             /* make attributes in array format */
5952       $propattr = array();
5953       foreach( $attr as $attribute ) {
5954         $attrsplit = explode( '=', $attribute, 2 );
5955         if( 1 < count( $attrsplit ))
5956           $propattr[$attrsplit[0]] = $attrsplit[1];
5957         else
5958           $propattr[] = $attribute;
5959       }
5960             /* call setProperty( $propname.. . */
5961       switch( strtoupper( $propname )) {
5962         case 'ATTENDEE':
5963           foreach( $propattr as $pix => $attr ) {
5964             if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' )))
5965               continue;
5966             $attr2 = explode( ',', $attr );
5967               if( 1 < count( $attr2 ))
5968                 $propattr[$pix] = $attr2;
5969           }
5970           $this->setProperty( $propname, $line, $propattr );
5971           break;
5972         case 'X-':
5973           $propname = ( isset( $propname2 )) ? $propname2 : $propname;
5974           unset( $propname2 );
5975         case 'CATEGORIES':
5976         case 'RESOURCES':
5977           if( FALSE !== strpos( $line, ',' )) {
5978             $content  = array( 0 => '' );
5979             $cix = $lix = 0;
5980             while( FALSE !== substr( $line, $lix, 1 )) {
5981               if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
5982                 $cix++;
5983                 $content[$cix] = '';
5984               }
5985               else
5986                 $content[$cix] .= $line[$lix];
5987               $lix++;
5988             }
5989             if( 1 < count( $content )) {
5990               $content = array_values( $content );
5991               foreach( $content as $cix => $contentPart )
5992                 $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
5993               $this->setProperty( $propname, $content, $propattr );
5994               break;
5995             }
5996             else
5997               $line = reset( $content );
5998           }
5999         case 'COMMENT':
6000         case 'CONTACT':
6001         case 'DESCRIPTION':
6002         case 'LOCATION':
6003         case 'SUMMARY':
6004           if( empty( $line ))
6005             $propattr = null;
6006           $this->setProperty( $propname, iCalUtilityFunctions::_strunrep( $line ), $propattr );
6007           break;
6008         case 'REQUEST-STATUS':
6009           $values    = explode( ';', $line, 3 );
6010           $values[1] = ( !isset( $values[1] )) ? null : iCalUtilityFunctions::_strunrep( $values[1] );
6011           $values[2] = ( !isset( $values[2] )) ? null : iCalUtilityFunctions::_strunrep( $values[2] );
6012           $this->setProperty( $propname
+6013                             , $values[0]  // statcode
+6014                             , $values[1]  // statdesc
+6015                             , $values[2]  // extdata
+6016                             , $propattr );
6017           break;
6018         case 'FREEBUSY':
6019           $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing
6020           unset( $propattr['FBTYPE'] );
6021           $values = explode( ',', $line );
6022           foreach( $values as $vix => $value ) {
6023             $value2 = explode( '/', $value );
6024             if( 1 < count( $value2 ))
6025               $values[$vix] = $value2;
6026           }
6027           $this->setProperty( $propname, $fbtype, $values, $propattr );
6028           break;
6029         case 'GEO':
6030           $value = explode( ';', $line, 2 );
6031           if( 2 > count( $value ))
6032             $value[1] = null;
6033           $this->setProperty( $propname, $value[0], $value[1], $propattr );
6034           break;
6035         case 'EXDATE':
6036           $values = ( !empty( $line )) ? explode( ',', $line ) : null;
6037           $this->setProperty( $propname, $values, $propattr );
6038           break;
6039         case 'RDATE':
6040           if( empty( $line )) {
6041             $this->setProperty( $propname, $line, $propattr );
6042             break;
6043           }
6044           $values = explode( ',', $line );
6045           foreach( $values as $vix => $value ) {
6046             $value2 = explode( '/', $value );
6047             if( 1 < count( $value2 ))
6048               $values[$vix] = $value2;
6049           }
6050           $this->setProperty( $propname, $values, $propattr );
6051           break;
6052         case 'EXRULE':
6053         case 'RRULE':
6054           $values = explode( ';', $line );
6055           $recur = array();
6056           foreach( $values as $value2 ) {
6057             if( empty( $value2 ))
6058               continue; // ;-char in ending position ???
6059             $value3 = explode( '=', $value2, 2 );
6060             $rulelabel = strtoupper( $value3[0] );
6061             switch( $rulelabel ) {
6062               case 'BYDAY': {
6063                 $value4 = explode( ',', $value3[1] );
6064                 if( 1 < count( $value4 )) {
6065                   foreach( $value4 as $v5ix => $value5 ) {
6066                     $value6 = array();
6067                     $dayno = $dayname = null;
6068                     $value5 = trim( (string) $value5 );
6069                     if(( ctype_alpha( substr( $value5, -1 ))) &&
+6070                        ( ctype_alpha( substr( $value5, -2, 1 )))) {
6071                       $dayname = substr( $value5, -2, 2 );
6072                       if( 2 < strlen( $value5 ))
6073                         $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
6074                     }
6075                     if( $dayno )
6076                       $value6[] = $dayno;
6077                     if( $dayname )
6078                       $value6['DAY'] = $dayname;
6079                     $value4[$v5ix] = $value6;
6080                   }
6081                 }
6082                 else {
6083                   $value4 = array();
6084                   $dayno  = $dayname = null;
6085                   $value5 = trim( (string) $value3[1] );
6086                   if(( ctype_alpha( substr( $value5, -1 ))) &&
+6087                      ( ctype_alpha( substr( $value5, -2, 1 )))) {
6088                       $dayname = substr( $value5, -2, 2 );
6089                     if( 2 < strlen( $value5 ))
6090                       $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
6091                   }
6092                   if( $dayno )
6093                     $value4[] = $dayno;
6094                   if( $dayname )
6095                     $value4['DAY'] = $dayname;
6096                 }
6097                 $recur[$rulelabel] = $value4;
6098                 break;
6099               }
6100               default: {
6101                 $value4 = explode( ',', $value3[1] );
6102                 if( 1 < count( $value4 ))
6103                   $value3[1] = $value4;
6104                 $recur[$rulelabel] = $value3[1];
6105                 break;
6106               }
6107             } // end - switch $rulelabel
6108           } // end - foreach( $values.. .
6109           $this->setProperty( $propname, $recur, $propattr );
6110           break;
6111         case 'ACTION':
6112         case 'CLASSIFICATION':
6113         case 'STATUS':
6114         case 'TRANSP':
6115         case 'UID':
6116         case 'TZID':
6117         case 'RELATED-TO':
6118         case 'TZNAME':
6119           $line = iCalUtilityFunctions::_strunrep( $line );
6120         default:
6121           $this->setProperty( $propname, $line, $propattr );
6122           break;
6123       } // end  switch( $propname.. .
6124     } // end - foreach( $proprows.. .
6125     unset( $unparsedtext, $this->unparsed, $proprows );
6126     if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
6127       $ckeys = array_keys( $this->components );
6128       foreach( $ckeys as $ckey ) {
6129         if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
6130           $this->components[$ckey]->parse();
6131         }
6132       }
6133     }
6134     return TRUE;
6135   }
6136 /*********************************************************************************/
6137 /*********************************************************************************/
6138 /**
6139  * return a copy of this component
6140  *
6141  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6142  * @since 2.15.4 - 2012-10-18
6143  * @return object
6144  */
6145   function copy() {
6146     return unserialize( serialize( $this ));
6147  }
6148 /*********************************************************************************/
6149 /*********************************************************************************/
6150 /**
6151  * delete calendar subcomponent from component container
6152  *
6153  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6154  * @since 2.8.8 - 2011-03-15
6155  * @param mixed $arg1 ordno / component type / component uid
6156  * @param mixed $arg2 optional, ordno if arg1 = component type
6157  * @return void
6158  */
6159   function deleteComponent( $arg1, $arg2=FALSE  ) {
6160     if( !isset( $this->components )) return FALSE;
6161     $argType = $index = null;
6162     if ( ctype_digit( (string) $arg1 )) {
6163       $argType = 'INDEX';
6164       $index   = (int) $arg1 - 1;
6165     }
6166     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
6167       $argType = strtolower( $arg1 );
6168       $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
6169     }
6170     $cix2dC = 0;
6171     foreach ( $this->components as $cix => $component) {
6172       if( empty( $component )) continue;
6173       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
6174         unset( $this->components[$cix] );
6175         return TRUE;
6176       }
6177       elseif( $argType == $component->objName ) {
6178         if( $index == $cix2dC ) {
6179           unset( $this->components[$cix] );
6180           return TRUE;
6181         }
6182         $cix2dC++;
6183       }
6184       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
6185         unset( $this->components[$cix] );
6186         return TRUE;
6187       }
6188     }
6189     return FALSE;
6190   }
6191 /**
6192  * get calendar component subcomponent from component container
6193  *
6194  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6195  * @since 2.8.8 - 2011-03-15
6196  * @param mixed $arg1 optional, ordno/component type/ component uid
6197  * @param mixed $arg2 optional, ordno if arg1 = component type
6198  * @return object
6199  */
6200   function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
6201     if( !isset( $this->components )) return FALSE;
6202     $index = $argType = null;
6203     if ( !$arg1 ) {
6204       $argType = 'INDEX';
6205       $index   = $this->compix['INDEX'] =
+6206         ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
6207     }
6208     elseif ( ctype_digit( (string) $arg1 )) {
6209       $argType = 'INDEX';
6210       $index   = (int) $arg1;
6211       unset( $this->compix );
6212     }
6213     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
6214       unset( $this->compix['INDEX'] );
6215       $argType = strtolower( $arg1 );
6216       if( !$arg2 )
6217         $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
6218       else
6219         $index = (int) $arg2;
6220     }
6221     $index  -= 1;
6222     $ckeys = array_keys( $this->components );
6223     if( !empty( $index) && ( $index > end( $ckeys )))
6224       return FALSE;
6225     $cix2gC = 0;
6226     foreach( $this->components as $cix => $component ) {
6227       if( empty( $component )) continue;
6228       if(( 'INDEX' == $argType ) && ( $index == $cix ))
6229         return $component->copy();
6230       elseif( $argType == $component->objName ) {
6231          if( $index == $cix2gC )
6232            return $component->copy();
6233          $cix2gC++;
6234       }
6235       elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
6236         return $component->copy();
6237     }
6238             /* not found.. . */
6239     unset( $this->compix );
6240     return false;
6241   }
6242 /**
6243  * add calendar component as subcomponent to container for subcomponents
6244  *
6245  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6246  * @since 1.x.x - 2007-04-24
6247  * @param object $component calendar component
6248  * @return void
6249  */
6250   function addSubComponent ( $component ) {
6251     $this->setComponent( $component );
6252   }
6253 /**
6254  * create new calendar component subcomponent, already included within component
6255  *
6256  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6257  * @since 2.6.33 - 2011-01-03
6258  * @param string $compType subcomponent type
6259  * @return object (reference)
6260  */
6261   function & newComponent( $compType ) {
6262     $config = $this->getConfig();
6263     $keys   = array_keys( $this->components );
6264     $ix     = end( $keys) + 1;
6265     switch( strtoupper( $compType )) {
6266       case 'ALARM':
6267       case 'VALARM':
6268         $this->components[$ix] = new valarm( $config );
6269         break;
6270       case 'STANDARD':
6271         array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
6272         $ix = 0;
6273         break;
6274       case 'DAYLIGHT':
6275         $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
6276         break;
6277       default:
6278         return FALSE;
6279     }
6280     return $this->components[$ix];
6281   }
6282 /**
6283  * add calendar component as subcomponent to container for subcomponents
6284  *
6285  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6286  * @since 2.8.8 - 2011-03-15
6287  * @param object $component calendar component
6288  * @param mixed $arg1 optional, ordno/component type/ component uid
6289  * @param mixed $arg2 optional, ordno if arg1 = component type
6290  * @return bool
6291  */
6292   function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
6293     if( !isset( $this->components )) return FALSE;
6294     $component->setConfig( $this->getConfig(), FALSE, TRUE );
6295     if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
6296             /* make sure dtstamp and uid is set */
6297       $dummy = $component->getProperty( 'dtstamp' );
6298       $dummy = $component->getProperty( 'uid' );
6299     }
6300     if( !$arg1 ) { // plain insert, last in chain
6301       $this->components[] = $component->copy();
6302       return TRUE;
6303     }
6304     $argType = $index = null;
6305     if ( ctype_digit( (string) $arg1 )) { // index insert/replace
6306       $argType = 'INDEX';
6307       $index   = (int) $arg1 - 1;
6308     }
6309     elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
6310       $argType = strtolower( $arg1 );
6311       $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
6312     }
6313     // else if arg1 is set, arg1 must be an UID
6314     $cix2sC = 0;
6315     foreach ( $this->components as $cix => $component2 ) {
6316       if( empty( $component2 )) continue;
6317       if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
6318         $this->components[$cix] = $component->copy();
6319         return TRUE;
6320       }
6321       elseif( $argType == $component2->objName ) { // component Type index insert/replace
6322         if( $index == $cix2sC ) {
6323           $this->components[$cix] = $component->copy();
6324           return TRUE;
6325         }
6326         $cix2sC++;
6327       }
6328       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
6329         $this->components[$cix] = $component->copy();
6330         return TRUE;
6331       }
6332     }
6333             /* arg1=index and not found.. . insert at index .. .*/
6334     if( 'INDEX' == $argType ) {
6335       $this->components[$index] = $component->copy();
6336       ksort( $this->components, SORT_NUMERIC );
6337     }
6338     else    /* not found.. . insert last in chain anyway .. .*/
6339     $this->components[] = $component->copy();
6340     return TRUE;
6341   }
6342 /**
6343  * creates formatted output for subcomponents
6344  *
6345  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6346  * @since 2.11.20 - 2012-02-06
6347  * @param array $xcaldecl
6348  * @return string
6349  */
6350   function createSubComponent() {
6351     $output = null;
6352     if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order
6353       $stdarr = $dlarr = array();
6354       foreach( $this->components as $component ) {
6355         if( empty( $component ))
6356           continue;
6357         $dt  = $component->getProperty( 'dtstart' );
6358         $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
6359         if( 'standard' == $component->objName ) {
6360           while( isset( $stdarr[$key] ))
6361             $key += 1;
6362           $stdarr[$key] = $component->copy();
6363         }
6364         elseif( 'daylight' == $component->objName ) {
6365           while( isset( $dlarr[$key] ))
6366             $key += 1;
6367           $dlarr[$key] = $component->copy();
6368         }
6369       } // end foreach( $this->components as $component )
6370       $this->components = array();
6371       ksort( $stdarr, SORT_NUMERIC );
6372       foreach( $stdarr as $std )
6373         $this->components[] = $std->copy();
6374       unset( $stdarr );
6375       ksort( $dlarr,  SORT_NUMERIC );
6376       foreach( $dlarr as $dl )
6377         $this->components[] = $dl->copy();
6378       unset( $dlarr );
6379     } // end if( 'vtimezone' == $this->objName )
6380     foreach( $this->components as $component ) {
6381       $component->setConfig( $this->getConfig(), FALSE, TRUE );
6382       $output .= $component->createComponent( $this->xcaldecl );
6383     }
6384     return $output;
6385   }
6386 }
6387 /*********************************************************************************/
6388 /*********************************************************************************/
6389 /**
6390  * class for calendar component VEVENT
6391  *
6392  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6393  * @since 2.5.1 - 2008-10-12
6394  */
6395 class vevent extends calendarComponent {
6396   var $attach;
6397   var $attendee;
6398   var $categories;
6399   var $comment;
6400   var $contact;
6401   var $class;
6402   var $created;
6403   var $description;
6404   var $dtend;
6405   var $dtstart;
6406   var $duration;
6407   var $exdate;
6408   var $exrule;
6409   var $geo;
6410   var $lastmodified;
6411   var $location;
6412   var $organizer;
6413   var $priority;
6414   var $rdate;
6415   var $recurrenceid;
6416   var $relatedto;
6417   var $requeststatus;
6418   var $resources;
6419   var $rrule;
6420   var $sequence;
6421   var $status;
6422   var $summary;
6423   var $transp;
6424   var $url;
6425   var $xprop;
6426             //  component subcomponents container
6427   var $components;
6428 /**
6429  * constructor for calendar component VEVENT object
6430  *
6431  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6432  * @since 2.8.2 - 2011-05-01
6433  * @param  array $config
6434  * @return void
6435  */
6436   function vevent( $config = array()) {
6437     $this->calendarComponent();
6438 
6439     $this->attach          = '';
6440     $this->attendee        = '';
6441     $this->categories      = '';
6442     $this->class           = '';
6443     $this->comment         = '';
6444     $this->contact         = '';
6445     $this->created         = '';
6446     $this->description     = '';
6447     $this->dtstart         = '';
6448     $this->dtend           = '';
6449     $this->duration        = '';
6450     $this->exdate          = '';
6451     $this->exrule          = '';
6452     $this->geo             = '';
6453     $this->lastmodified    = '';
6454     $this->location        = '';
6455     $this->organizer       = '';
6456     $this->priority        = '';
6457     $this->rdate           = '';
6458     $this->recurrenceid    = '';
6459     $this->relatedto       = '';
6460     $this->requeststatus   = '';
6461     $this->resources       = '';
6462     $this->rrule           = '';
6463     $this->sequence        = '';
6464     $this->status          = '';
6465     $this->summary         = '';
6466     $this->transp          = '';
6467     $this->url             = '';
6468     $this->xprop           = '';
6469 
6470     $this->components      = array();
6471 
6472     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6473                                           $config['language']   = ICAL_LANG;
6474     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6475     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6476     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6477     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6478     $this->setConfig( $config );
6479 
6480   }
6481 /**
6482  * create formatted output for calendar component VEVENT object instance
6483  *
6484  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6485  * @since 2.10.16 - 2011-10-28
6486  * @param array $xcaldecl
6487  * @return string
6488  */
6489   function createComponent( &$xcaldecl ) {
6490     $objectname    = $this->_createFormat();
6491     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6492     $component    .= $this->createUid();
6493     $component    .= $this->createDtstamp();
6494     $component    .= $this->createAttach();
6495     $component    .= $this->createAttendee();
6496     $component    .= $this->createCategories();
6497     $component    .= $this->createComment();
6498     $component    .= $this->createContact();
6499     $component    .= $this->createClass();
6500     $component    .= $this->createCreated();
6501     $component    .= $this->createDescription();
6502     $component    .= $this->createDtstart();
6503     $component    .= $this->createDtend();
6504     $component    .= $this->createDuration();
6505     $component    .= $this->createExdate();
6506     $component    .= $this->createExrule();
6507     $component    .= $this->createGeo();
6508     $component    .= $this->createLastModified();
6509     $component    .= $this->createLocation();
6510     $component    .= $this->createOrganizer();
6511     $component    .= $this->createPriority();
6512     $component    .= $this->createRdate();
6513     $component    .= $this->createRrule();
6514     $component    .= $this->createRelatedTo();
6515     $component    .= $this->createRequestStatus();
6516     $component    .= $this->createRecurrenceid();
6517     $component    .= $this->createResources();
6518     $component    .= $this->createSequence();
6519     $component    .= $this->createStatus();
6520     $component    .= $this->createSummary();
6521     $component    .= $this->createTransp();
6522     $component    .= $this->createUrl();
6523     $component    .= $this->createXprop();
6524     $component    .= $this->createSubComponent();
6525     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6526     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6527       foreach( $this->xcaldecl as $localxcaldecl )
6528         $xcaldecl[] = $localxcaldecl;
6529     }
6530     return $component;
6531   }
6532 }
6533 /*********************************************************************************/
6534 /*********************************************************************************/
6535 /**
6536  * class for calendar component VTODO
6537  *
6538  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6539  * @since 2.5.1 - 2008-10-12
6540  */
6541 class vtodo extends calendarComponent {
6542   var $attach;
6543   var $attendee;
6544   var $categories;
6545   var $comment;
6546   var $completed;
6547   var $contact;
6548   var $class;
6549   var $created;
6550   var $description;
6551   var $dtstart;
6552   var $due;
6553   var $duration;
6554   var $exdate;
6555   var $exrule;
6556   var $geo;
6557   var $lastmodified;
6558   var $location;
6559   var $organizer;
6560   var $percentcomplete;
6561   var $priority;
6562   var $rdate;
6563   var $recurrenceid;
6564   var $relatedto;
6565   var $requeststatus;
6566   var $resources;
6567   var $rrule;
6568   var $sequence;
6569   var $status;
6570   var $summary;
6571   var $url;
6572   var $xprop;
6573             //  component subcomponents container
6574   var $components;
6575 /**
6576  * constructor for calendar component VTODO object
6577  *
6578  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6579  * @since 2.8.2 - 2011-05-01
6580  * @param array $config
6581  * @return void
6582  */
6583   function vtodo( $config = array()) {
6584     $this->calendarComponent();
6585 
6586     $this->attach          = '';
6587     $this->attendee        = '';
6588     $this->categories      = '';
6589     $this->class           = '';
6590     $this->comment         = '';
6591     $this->completed       = '';
6592     $this->contact         = '';
6593     $this->created         = '';
6594     $this->description     = '';
6595     $this->dtstart         = '';
6596     $this->due             = '';
6597     $this->duration        = '';
6598     $this->exdate          = '';
6599     $this->exrule          = '';
6600     $this->geo             = '';
6601     $this->lastmodified    = '';
6602     $this->location        = '';
6603     $this->organizer       = '';
6604     $this->percentcomplete = '';
6605     $this->priority        = '';
6606     $this->rdate           = '';
6607     $this->recurrenceid    = '';
6608     $this->relatedto       = '';
6609     $this->requeststatus   = '';
6610     $this->resources       = '';
6611     $this->rrule           = '';
6612     $this->sequence        = '';
6613     $this->status          = '';
6614     $this->summary         = '';
6615     $this->url             = '';
6616     $this->xprop           = '';
6617 
6618     $this->components      = array();
6619 
6620     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6621                                           $config['language']   = ICAL_LANG;
6622     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6623     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6624     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6625     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6626     $this->setConfig( $config );
6627 
6628   }
6629 /**
6630  * create formatted output for calendar component VTODO object instance
6631  *
6632  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6633  * @since 2.5.1 - 2008-11-07
6634  * @param array $xcaldecl
6635  * @return string
6636  */
6637   function createComponent( &$xcaldecl ) {
6638     $objectname    = $this->_createFormat();
6639     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6640     $component    .= $this->createUid();
6641     $component    .= $this->createDtstamp();
6642     $component    .= $this->createAttach();
6643     $component    .= $this->createAttendee();
6644     $component    .= $this->createCategories();
6645     $component    .= $this->createClass();
6646     $component    .= $this->createComment();
6647     $component    .= $this->createCompleted();
6648     $component    .= $this->createContact();
6649     $component    .= $this->createCreated();
6650     $component    .= $this->createDescription();
6651     $component    .= $this->createDtstart();
6652     $component    .= $this->createDue();
6653     $component    .= $this->createDuration();
6654     $component    .= $this->createExdate();
6655     $component    .= $this->createExrule();
6656     $component    .= $this->createGeo();
6657     $component    .= $this->createLastModified();
6658     $component    .= $this->createLocation();
6659     $component    .= $this->createOrganizer();
6660     $component    .= $this->createPercentComplete();
6661     $component    .= $this->createPriority();
6662     $component    .= $this->createRdate();
6663     $component    .= $this->createRelatedTo();
6664     $component    .= $this->createRequestStatus();
6665     $component    .= $this->createRecurrenceid();
6666     $component    .= $this->createResources();
6667     $component    .= $this->createRrule();
6668     $component    .= $this->createSequence();
6669     $component    .= $this->createStatus();
6670     $component    .= $this->createSummary();
6671     $component    .= $this->createUrl();
6672     $component    .= $this->createXprop();
6673     $component    .= $this->createSubComponent();
6674     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6675     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6676       foreach( $this->xcaldecl as $localxcaldecl )
6677         $xcaldecl[] = $localxcaldecl;
6678     }
6679     return $component;
6680   }
6681 }
6682 /*********************************************************************************/
6683 /*********************************************************************************/
6684 /**
6685  * class for calendar component VJOURNAL
6686  *
6687  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6688  * @since 2.5.1 - 2008-10-12
6689  */
6690 class vjournal extends calendarComponent {
6691   var $attach;
6692   var $attendee;
6693   var $categories;
6694   var $comment;
6695   var $contact;
6696   var $class;
6697   var $created;
6698   var $description;
6699   var $dtstart;
6700   var $exdate;
6701   var $exrule;
6702   var $lastmodified;
6703   var $organizer;
6704   var $rdate;
6705   var $recurrenceid;
6706   var $relatedto;
6707   var $requeststatus;
6708   var $rrule;
6709   var $sequence;
6710   var $status;
6711   var $summary;
6712   var $url;
6713   var $xprop;
6714 /**
6715  * constructor for calendar component VJOURNAL object
6716  *
6717  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6718  * @since 2.8.2 - 2011-05-01
6719  * @param array $config
6720  * @return void
6721  */
6722   function vjournal( $config = array()) {
6723     $this->calendarComponent();
6724 
6725     $this->attach          = '';
6726     $this->attendee        = '';
6727     $this->categories      = '';
6728     $this->class           = '';
6729     $this->comment         = '';
6730     $this->contact         = '';
6731     $this->created         = '';
6732     $this->description     = '';
6733     $this->dtstart         = '';
6734     $this->exdate          = '';
6735     $this->exrule          = '';
6736     $this->lastmodified    = '';
6737     $this->organizer       = '';
6738     $this->rdate           = '';
6739     $this->recurrenceid    = '';
6740     $this->relatedto       = '';
6741     $this->requeststatus   = '';
6742     $this->rrule           = '';
6743     $this->sequence        = '';
6744     $this->status          = '';
6745     $this->summary         = '';
6746     $this->url             = '';
6747     $this->xprop           = '';
6748 
6749     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6750                                           $config['language']   = ICAL_LANG;
6751     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6752     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6753     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6754     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6755     $this->setConfig( $config );
6756 
6757   }
6758 /**
6759  * create formatted output for calendar component VJOURNAL object instance
6760  *
6761  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6762  * @since 2.5.1 - 2008-10-12
6763  * @param array $xcaldecl
6764  * @return string
6765  */
6766   function createComponent( &$xcaldecl ) {
6767     $objectname = $this->_createFormat();
6768     $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6769     $component .= $this->createUid();
6770     $component .= $this->createDtstamp();
6771     $component .= $this->createAttach();
6772     $component .= $this->createAttendee();
6773     $component .= $this->createCategories();
6774     $component .= $this->createClass();
6775     $component .= $this->createComment();
6776     $component .= $this->createContact();
6777     $component .= $this->createCreated();
6778     $component .= $this->createDescription();
6779     $component .= $this->createDtstart();
6780     $component .= $this->createExdate();
6781     $component .= $this->createExrule();
6782     $component .= $this->createLastModified();
6783     $component .= $this->createOrganizer();
6784     $component .= $this->createRdate();
6785     $component .= $this->createRequestStatus();
6786     $component .= $this->createRecurrenceid();
6787     $component .= $this->createRelatedTo();
6788     $component .= $this->createRrule();
6789     $component .= $this->createSequence();
6790     $component .= $this->createStatus();
6791     $component .= $this->createSummary();
6792     $component .= $this->createUrl();
6793     $component .= $this->createXprop();
6794     $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
6795     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6796       foreach( $this->xcaldecl as $localxcaldecl )
6797         $xcaldecl[] = $localxcaldecl;
6798     }
6799     return $component;
6800   }
6801 }
6802 /*********************************************************************************/
6803 /*********************************************************************************/
6804 /**
6805  * class for calendar component VFREEBUSY
6806  *
6807  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6808  * @since 2.5.1 - 2008-10-12
6809  */
6810 class vfreebusy extends calendarComponent {
6811   var $attendee;
6812   var $comment;
6813   var $contact;
6814   var $dtend;
6815   var $dtstart;
6816   var $duration;
6817   var $freebusy;
6818   var $organizer;
6819   var $requeststatus;
6820   var $url;
6821   var $xprop;
6822             //  component subcomponents container
6823   var $components;
6824 /**
6825  * constructor for calendar component VFREEBUSY object
6826  *
6827  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6828  * @since 2.8.2 - 2011-05-01
6829  * @param array $config
6830  * @return void
6831  */
6832   function vfreebusy( $config = array()) {
6833     $this->calendarComponent();
6834 
6835     $this->attendee        = '';
6836     $this->comment         = '';
6837     $this->contact         = '';
6838     $this->dtend           = '';
6839     $this->dtstart         = '';
6840     $this->duration        = '';
6841     $this->freebusy        = '';
6842     $this->organizer       = '';
6843     $this->requeststatus   = '';
6844     $this->url             = '';
6845     $this->xprop           = '';
6846 
6847     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6848                                           $config['language']   = ICAL_LANG;
6849     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6850     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6851     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6852     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6853     $this->setConfig( $config );
6854 
6855   }
6856 /**
6857  * create formatted output for calendar component VFREEBUSY object instance
6858  *
6859  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6860  * @since 2.3.1 - 2007-11-19
6861  * @param array $xcaldecl
6862  * @return string
6863  */
6864   function createComponent( &$xcaldecl ) {
6865     $objectname = $this->_createFormat();
6866     $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6867     $component .= $this->createUid();
6868     $component .= $this->createDtstamp();
6869     $component .= $this->createAttendee();
6870     $component .= $this->createComment();
6871     $component .= $this->createContact();
6872     $component .= $this->createDtstart();
6873     $component .= $this->createDtend();
6874     $component .= $this->createDuration();
6875     $component .= $this->createFreebusy();
6876     $component .= $this->createOrganizer();
6877     $component .= $this->createRequestStatus();
6878     $component .= $this->createUrl();
6879     $component .= $this->createXprop();
6880     $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
6881     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6882       foreach( $this->xcaldecl as $localxcaldecl )
6883         $xcaldecl[] = $localxcaldecl;
6884     }
6885     return $component;
6886   }
6887 }
6888 /*********************************************************************************/
6889 /*********************************************************************************/
6890 /**
6891  * class for calendar component VALARM
6892  *
6893  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6894  * @since 2.5.1 - 2008-10-12
6895  */
6896 class valarm extends calendarComponent {
6897   var $action;
6898   var $attach;
6899   var $attendee;
6900   var $description;
6901   var $duration;
6902   var $repeat;
6903   var $summary;
6904   var $trigger;
6905   var $xprop;
6906 /**
6907  * constructor for calendar component VALARM object
6908  *
6909  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6910  * @since 2.8.2 - 2011-05-01
6911  * @param array $config
6912  * @return void
6913  */
6914   function valarm( $config = array()) {
6915     $this->calendarComponent();
6916 
6917     $this->action          = '';
6918     $this->attach          = '';
6919     $this->attendee        = '';
6920     $this->description     = '';
6921     $this->duration        = '';
6922     $this->repeat          = '';
6923     $this->summary         = '';
6924     $this->trigger         = '';
6925     $this->xprop           = '';
6926 
6927     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6928                                           $config['language']   = ICAL_LANG;
6929     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6930     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6931     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6932     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6933     $this->setConfig( $config );
6934 
6935   }
6936 /**
6937  * create formatted output for calendar component VALARM object instance
6938  *
6939  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6940  * @since 2.5.1 - 2008-10-22
6941  * @param array $xcaldecl
6942  * @return string
6943  */
6944   function createComponent( &$xcaldecl ) {
6945     $objectname    = $this->_createFormat();
6946     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6947     $component    .= $this->createAction();
6948     $component    .= $this->createAttach();
6949     $component    .= $this->createAttendee();
6950     $component    .= $this->createDescription();
6951     $component    .= $this->createDuration();
6952     $component    .= $this->createRepeat();
6953     $component    .= $this->createSummary();
6954     $component    .= $this->createTrigger();
6955     $component    .= $this->createXprop();
6956     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6957     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6958       foreach( $this->xcaldecl as $localxcaldecl )
6959         $xcaldecl[] = $localxcaldecl;
6960     }
6961     return $component;
6962   }
6963 }
6964 /**********************************************************************************
6965 /*********************************************************************************/
6966 /**
6967  * class for calendar component VTIMEZONE
6968  *
6969  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6970  * @since 2.5.1 - 2008-10-12
6971  */
6972 class vtimezone extends calendarComponent {
6973   var $timezonetype;
6974 
6975   var $comment;
6976   var $dtstart;
6977   var $lastmodified;
6978   var $rdate;
6979   var $rrule;
6980   var $tzid;
6981   var $tzname;
6982   var $tzoffsetfrom;
6983   var $tzoffsetto;
6984   var $tzurl;
6985   var $xprop;
6986             //  component subcomponents container
6987   var $components;
6988 /**
6989  * constructor for calendar component VTIMEZONE object
6990  *
6991  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6992  * @since 2.8.2 - 2011-05-01
6993  * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT )
6994  * @param array $config
6995  * @return void
6996  */
6997   function vtimezone( $timezonetype=FALSE, $config = array()) {
6998     if( is_array( $timezonetype )) {
6999       $config       = $timezonetype;
7000       $timezonetype = FALSE;
7001     }
7002     if( !$timezonetype )
7003       $this->timezonetype = 'VTIMEZONE';
7004     else
7005       $this->timezonetype = strtoupper( $timezonetype );
7006     $this->calendarComponent();
7007 
7008     $this->comment         = '';
7009     $this->dtstart         = '';
7010     $this->lastmodified    = '';
7011     $this->rdate           = '';
7012     $this->rrule           = '';
7013     $this->tzid            = '';
7014     $this->tzname          = '';
7015     $this->tzoffsetfrom    = '';
7016     $this->tzoffsetto      = '';
7017     $this->tzurl           = '';
7018     $this->xprop           = '';
7019 
7020     $this->components      = array();
7021 
7022     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
7023                                           $config['language']   = ICAL_LANG;
7024     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
7025     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
7026     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
7027     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
7028     $this->setConfig( $config );
7029 
7030   }
7031 /**
7032  * create formatted output for calendar component VTIMEZONE object instance
7033  *
7034  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7035  * @since 2.5.1 - 2008-10-25
7036  * @param array $xcaldecl
7037  * @return string
7038  */
7039   function createComponent( &$xcaldecl ) {
7040     $objectname    = $this->_createFormat();
7041     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
7042     $component    .= $this->createTzid();
7043     $component    .= $this->createLastModified();
7044     $component    .= $this->createTzurl();
7045     $component    .= $this->createDtstart();
7046     $component    .= $this->createTzoffsetfrom();
7047     $component    .= $this->createTzoffsetto();
7048     $component    .= $this->createComment();
7049     $component    .= $this->createRdate();
7050     $component    .= $this->createRrule();
7051     $component    .= $this->createTzname();
7052     $component    .= $this->createXprop();
7053     $component    .= $this->createSubComponent();
7054     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
7055     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
7056       foreach( $this->xcaldecl as $localxcaldecl )
7057         $xcaldecl[] = $localxcaldecl;
7058     }
7059     return $component;
7060   }
7061 }
7062 /*********************************************************************************/
7063 /*********************************************************************************/
7064 /**
7065  * moving all utility (static) functions to a utility class
7066  * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file
7067  *
7068  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7069  * @since 2.10.1 - 2011-07-16
7070  *
7071  */
7072 class iCalUtilityFunctions {
7073   // Store the single instance of iCalUtilityFunctions
7074   private static $m_pInstance;
7075 
7076   // Private constructor to limit object instantiation to within the class
7077   private function __construct() {
7078     $m_pInstance = FALSE;
7079   }
7080 
7081   // Getter method for creating/returning the single instance of this class
7082   public static function getInstance() {
7083     if (!self::$m_pInstance)
7084       self::$m_pInstance = new iCalUtilityFunctions();
7085 
7086     return self::$m_pInstance;
7087   }
7088 /**
7089  * ensures internal date-time/date format (keyed array) for an input date-time/date array (keyed or unkeyed)
7090  *
7091  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7092  * @since 2.14.1 - 2012-09-27
7093  * @param array $datetime
7094  * @param int $parno optional, default FALSE
7095  * @return array
7096  */
7097   public static function _date_time_array( $datetime, $parno=FALSE ) {
7098     return iCalUtilityFunctions::_chkDateArr( $datetime, $parno );
7099   }
7100   public static function _chkDateArr( $datetime, $parno=FALSE ) {
7101     $output = array();
7102     foreach( $datetime as $dateKey => $datePart ) {
7103       switch ( $dateKey ) {
7104         case '0': case 'year':   $output['year']  = $datePart; break;
7105         case '1': case 'month':  $output['month'] = $datePart; break;
7106         case '2': case 'day':    $output['day']   = $datePart; break;
7107       }
7108       if( 3 != $parno ) {
7109         switch ( $dateKey ) {
7110           case '0':
7111           case '1':
7112           case '2': break;
7113           case '3': case 'hour': $output['hour']  = $datePart; break;
7114           case '4': case 'min' : $output['min']   = $datePart; break;
7115           case '5': case 'sec' : $output['sec']   = $datePart; break;
7116           case '6': case 'tz'  : $output['tz']    = $datePart; break;
7117         }
7118       }
7119     }
7120     if( 3 != $parno ) {
7121       if( !isset( $output['hour'] ))         $output['hour'] = 0;
7122       if( !isset( $output['min']  ))         $output['min']  = 0;
7123       if( !isset( $output['sec']  ))         $output['sec']  = 0;
7124       if( isset( $output['tz'] ) &&
+7125         (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
7126                                              $output['tz']   = 'Z';
7127     }
7128     return $output;
7129   }
7130 /**
7131  * check date(-time) and params arrays for an opt. timezone and if it is a DATE-TIME or DATE (updates $parno and params)
7132  *
7133  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7134  * @since 2.10.30 - 2012-01-16
7135  * @param array $date, date to check
7136  * @param int $parno, no of date parts (i.e. year, month.. .)
7137  * @param array $params, property parameters
7138  * @return void
7139  */
7140   public static function _chkdatecfg( $theDate, & $parno, & $params ) {
7141     if( isset( $params['TZID'] ))
7142       $parno = 6;
7143     elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
7144       $parno = 3;
7145     else {
7146       if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
7147         $parno = 7;
7148       if( is_array( $theDate )) {
7149         if( isset( $theDate['timestamp'] ))
7150           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
7151         else
7152           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
7153         if( !empty( $tzid )) {
7154           $parno = 7;
7155           if( !iCalUtilityFunctions::_isOffset( $tzid ))
7156             $params['TZID'] = $tzid; // save only timezone
7157         }
7158         elseif( !$parno && ( 3 == count( $theDate )) &&
+7159           ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
7160           $parno = 3;
7161         else
7162           $parno = 6;
7163       }
7164       else { // string
7165         $date = trim( $theDate );
7166         if( 'Z' == substr( $date, -1 ))
7167           $parno = 7; // UTC DATE-TIME
7168         elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
+7169           ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
7170           $parno = 3; // DATE
7171         $date = iCalUtilityFunctions::_strdate2date( $date, $parno );
7172         unset( $date['unparsedtext'] );
7173         if( !empty( $date['tz'] )) {
7174           $parno = 7;
7175           if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
7176             $params['TZID'] = $date['tz']; // save only timezone
7177         }
7178         elseif( empty( $parno ))
7179           $parno = 6;
7180       }
7181       if( isset( $params['TZID'] ))
7182         $parno = 6;
7183     }
7184   }
7185 /**
7186  * vcalendar sort callback function
7187  *
7188  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7189  * @since 2.16.2 - 2012-12-17
7190  * @param array $a
7191  * @param array $b
7192  * @return int
7193  */
7194   public static function _cmpfcn( $a, $b ) {
7195     if(        empty( $a ))                       return -1;
7196     if(        empty( $b ))                       return  1;
7197     if( 'vtimezone' == $a->objName ) {
7198       if( 'vtimezone' != $b->objName )            return -1;
7199       elseif( $a->srtk[0] <= $b->srtk[0] )        return -1;
7200       else                                        return  1;
7201     }
7202     elseif( 'vtimezone' == $b->objName )          return  1;
7203     $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
7204     for( $k = 0; $k < 4 ; $k++ ) {
7205       if(        empty( $a->srtk[$k] ))           return -1;
7206       elseif(    empty( $b->srtk[$k] ))           return  1;
7207       if( is_array( $a->srtk[$k] )) {
7208         if( is_array( $b->srtk[$k] )) {
7209           foreach( $sortkeys as $key ) {
7210             if    ( !isset( $a->srtk[$k][$key] )) return -1;
7211             elseif( !isset( $b->srtk[$k][$key] )) return  1;
7212             if    (  empty( $a->srtk[$k][$key] )) return -1;
7213             elseif(  empty( $b->srtk[$k][$key] )) return  1;
7214             if    (         $a->srtk[$k][$key] == $b->srtk[$k][$key])
7215                                                   continue;
7216             if    ((  (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
7217                                                   return -1;
7218             elseif((  (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
7219                                                   return  1;
7220           }
7221         }
7222         else                                      return -1;
7223       }
7224       elseif( is_array( $b->srtk[$k] ))           return  1;
7225       elseif( $a->srtk[$k] < $b->srtk[$k] )       return -1;
7226       elseif( $a->srtk[$k] > $b->srtk[$k] )       return  1;
7227     }
7228     return 0;
7229   }
7230 /**
7231  * byte oriented line folding fix
7232  *
7233  * remove any line-endings that may include spaces or tabs
7234  * and convert all line endings (iCal default '\r\n'),
7235  * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
7236  *
7237  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7238  * @since 2.12.17 - 2012-07-12
7239  * @param string $text
7240  * @param string $nl
7241  * @return string
7242  */
7243   public static function convEolChar( & $text, $nl ) {
7244     $outp = '';
7245     $cix  = 0;
7246     while(    isset(   $text[$cix] )) {
7247       if(     isset(   $text[$cix + 2] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] ) &&
+7248         ((    " " ==   $text[$cix + 2] ) ||  ( "\t" == $text[$cix + 2] )))                    // 2 pos eolchar + ' ' or '\t'
7249         $cix  += 2;                                                                           // skip 3
7250       elseif( isset(   $text[$cix + 1] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] )) {
7251         $outp .= $nl;                                                                         // 2 pos eolchar
7252         $cix  += 1;                                                                           // replace with $nl
7253       }
7254       elseif( isset(   $text[$cix + 1] ) && (( "\r" == $text[$cix] ) || ( "\n" == $text[$cix] )) &&
+7255            (( " " ==   $text[$cix + 1] ) ||  ( "\t" == $text[$cix + 1] )))                     // 1 pos eolchar + ' ' or '\t'
7256         $cix  += 1;                                                                            // skip 2
7257       elseif(( "\r" == $text[$cix] )     ||  ( "\n" == $text[$cix] ))                          // 1 pos eolchar
7258         $outp .= $nl;                                                                          // replace with $nl
7259       else
7260         $outp .= $text[$cix];                                                                  // add any other byte
7261       $cix    += 1;
7262     }
7263     return $outp;
7264   }
7265 /**
7266  * create a calendar timezone and standard/daylight components
7267  *
7268  * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
7269  *
7270  * BEGIN:VTIMEZONE
7271  * TZID:Europe/Stockholm
7272  * BEGIN:STANDARD
7273  * DTSTART:20101031T020000
7274  * TZOFFSETFROM:+0200
7275  * TZOFFSETTO:+0100
7276  * TZNAME:CET
7277  * END:STANDARD
7278  * BEGIN:DAYLIGHT
7279  * DTSTART:20100328T030000
7280  * TZOFFSETFROM:+0100
7281  * TZOFFSETTO:+0200
7282  * TZNAME:CEST
7283  * END:DAYLIGHT
7284  * END:VTIMEZONE
7285  *
7286  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7287  * @since 2.16.1 - 2012-11-26
7288  * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
7289  * Additional changes jpirkey
7290  * @param object $calendar, reference to an iCalcreator calendar instance
7291  * @param string $timezone, a PHP5 (DateTimeZone) valid timezone
7292  * @param array  $xProp,    *[x-propName => x-propValue], optional
7293  * @param int    $from      a unix timestamp
7294  * @param int    $to        a unix timestamp
7295  * @return bool
7296  */
7297   public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
7298     if( empty( $timezone ))
7299       return FALSE;
7300     if( !empty( $from ) && !is_int( $from ))
7301       return FALSE;
7302     if( !empty( $to )   && !is_int( $to ))
7303       return FALSE;
7304     try {
7305       $dtz               = new DateTimeZone( $timezone );
7306       $transitions       = $dtz->getTransitions();
7307       $utcTz             = new DateTimeZone( 'UTC' );
7308     }
7309     catch( Exception $e ) { return FALSE; }
7310     if( empty( $to )) {
7311       $dates             = array_keys( $calendar->getProperty( 'dtstart' ));
7312       if( empty( $dates ))
7313         $dates           = array( date( 'Ymd' ));
7314     }
7315     if( !empty( $from ))
7316       $dateFrom          = new DateTime( "@$from" );             // set lowest date (UTC)
7317     else {
7318       $from              = reset( $dates );                      // set lowest date to the lowest dtstart date
7319       $dateFrom          = new DateTime( $from.'T000000', $dtz );
7320       $dateFrom->modify( '-1 month' );                           // set $dateFrom to one month before the lowest date
7321       $dateFrom->setTimezone( $utcTz );                          // convert local date to UTC
7322     }
7323     $dateFromYmd         = $dateFrom->format('Y-m-d' );
7324     if( !empty( $to ))
7325       $dateTo            = new DateTime( "@$to" );               // set end date (UTC)
7326     else {
7327       $to                = end( $dates );                        // set highest date to the highest dtstart date
7328       $dateTo            = new DateTime( $to.'T235959', $dtz );
7329       $dateTo->modify( '+1 year' );                              // set $dateTo to one year after the highest date
7330       $dateTo->setTimezone( $utcTz );                            // convert local date to UTC
7331     }
7332     $dateToYmd           = $dateTo->format('Y-m-d' );
7333     unset( $dtz );
7334     $transTemp           = array();
7335     $prevOffsetfrom      = 0;
7336     $stdIx  = $dlghtIx   = null;
7337     $prevTrans           = FALSE;
7338     foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!!
7339       $date              = new DateTime( "@{$trans['ts']}" );    // set transition date (UTC)
7340       $transDateYmd      = $date->format('Y-m-d' );
7341       if ( $transDateYmd < $dateFromYmd ) {
7342         $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom
7343         $prevTrans       = $trans;                               // save it in case we don't find any that match
7344         $prevTrans['offsetfrom'] = ( 0 < $tix ) ? $transitions[$tix-1]['offset'] : 0;
7345         continue;
7346       }
7347       if( $transDateYmd > $dateToYmd )
7348         break;                                                   // loop always (?) breaks here
7349       if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
7350         $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom
7351         $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date
7352         $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart and (opt) rdate setting
7353         $d = explode( '-', $d );
7354         $trans['time']   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
7355       }
7356       $prevOffsetfrom    = $trans['offset'];
7357       if( TRUE !== $trans['isdst'] ) {                           // standard timezone
7358         if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any repeating rdate's (in order)
+7359            ( $transTemp[$stdIx]['abbr']       ==   $trans['abbr'] )        &&
+7360            ( $transTemp[$stdIx]['offsetfrom'] ==   $trans['offsetfrom'] )  &&
+7361            ( $transTemp[$stdIx]['offset']     ==   $trans['offset'] )) {
7362           $transTemp[$stdIx]['rdate'][]        =   $trans['time'];
7363           continue;
7364         }
7365         $stdIx           = $tix;
7366       } // end standard timezone
7367       else {                                                     // daylight timezone
7368         if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any repeating rdate's (in order)
+7369            ( $transTemp[$dlghtIx]['abbr']       ==   $trans['abbr'] )         &&
+7370            ( $transTemp[$dlghtIx]['offsetfrom'] ==   $trans['offsetfrom'] )   &&
+7371            ( $transTemp[$dlghtIx]['offset']     ==   $trans['offset'] )) {
7372           $transTemp[$dlghtIx]['rdate'][]        =   $trans['time'];
7373           continue;
7374         }
7375         $dlghtIx         = $tix;
7376       } // end daylight timezone
7377       $transTemp[$tix]   = $trans;
7378     } // end foreach( $transitions as $tix => $trans )
7379     $tz  = & $calendar->newComponent( 'vtimezone' );
7380     $tz->setproperty( 'tzid', $timezone );
7381     if( !empty( $xProp )) {
7382       foreach( $xProp as $xPropName => $xPropValue )
7383         if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
7384           $tz->setproperty( $xPropName, $xPropValue );
7385     }
7386     if( empty( $transTemp )) {      // if no match found
7387       if( $prevTrans ) {            // then we use the last transition (before startdate) for the tz info
7388         $date = new DateTime( "@{$prevTrans['ts']}" );           // set transition date (UTC)
7389         $date->modify( $prevTrans['offsetfrom'].'seconds' );     // convert utc date to local date
7390         $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart setting
7391         $d = explode( '-', $d );
7392         $prevTrans['time'] = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
7393         $transTemp[0] = $prevTrans;
7394       }
7395       else {                        // or we use the timezone identifier to BUILD the standard tz info (?)
7396         $date = new DateTime( 'now', new DateTimeZone( $timezone ));
7397         $transTemp[0] = array( 'time'       => $date->format( 'Y-m-d\TH:i:s O' )
+7398                              , 'offset'     => $date->format( 'Z' )
+7399                              , 'offsetfrom' => $date->format( 'Z' )
+7400                              , 'isdst'      => FALSE );
7401       }
7402     }
7403     unset( $transitions, $date, $prevTrans );
7404     foreach( $transTemp as $tix => $trans ) {
7405       $type  = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
7406       $scomp = & $tz->newComponent( $type );
7407       $scomp->setProperty( 'dtstart',         $trans['time'] );
7408 //      $scomp->setProperty( 'x-utc-timestamp', $tix.' : '.$trans['ts'] );   // test ###
7409       if( !empty( $trans['abbr'] ))
7410         $scomp->setProperty( 'tzname',        $trans['abbr'] );
7411       if( isset( $trans['offsetfrom'] ))
7412         $scomp->setProperty( 'tzoffsetfrom',  iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
7413       $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
7414       if( isset( $trans['rdate'] ))
7415         $scomp->setProperty( 'RDATE',         $trans['rdate'] );
7416     }
7417     return TRUE;
7418   }
7419 /**
7420  * creates formatted output for calendar component property data value type date/date-time
7421  *
7422  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7423  * @since 2.14.1 - 2012-09-17
7424  * @param array   $datetime
7425  * @param int     $parno, optional, default 6
7426  * @return string
7427  */
7428   public static function _format_date_time( $datetime, $parno=6 ) {
7429     return iCalUtilityFunctions::_date2strdate( $datetime, $parno );
7430   }
7431   public static function _date2strdate( $datetime, $parno=6 ) {
7432     if( !isset( $datetime['year'] )  &&
7433         !isset( $datetime['month'] ) &&
7434         !isset( $datetime['day'] )   &&
7435         !isset( $datetime['hour'] )  &&
7436         !isset( $datetime['min'] )   &&
7437         !isset( $datetime['sec'] ))
7438       return;
7439     $output     = null;
7440     foreach( $datetime as $dkey => & $dvalue )
7441       if( 'tz' != $dkey ) $dvalue = (integer) $dvalue;
7442     $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] );
7443     if( 3 == $parno )
7444       return $output;
7445     if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
7446     if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
7447     if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
7448     $output    .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] );
7449     if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
7450       $datetime['tz'] = trim( $datetime['tz'] );
7451       if( 'Z'  == $datetime['tz'] )
7452         $parno  = 7;
7453       elseif( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
7454         $parno  = 7;
7455         $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
7456         try {
7457           $d    = new DateTime( $output, new DateTimeZone( 'UTC' ));
7458           if( 0 != $offset ) // adjust för offset
7459             $d->modify( "$offset seconds" );
7460           $output = $d->format( 'Ymd\THis' );
7461         }
7462         catch( Exception $e ) {
7463           $output = date( 'Ymd\THis', mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year'] ));
7464         }
7465       }
7466       if( 7 == $parno )
7467         $output .= 'Z';
7468     }
7469     return $output;
7470   }
7471 /**
7472  * convert a date/datetime (array) to timestamp
7473  *
7474  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7475  * @since 2.14.1 - 2012-09-29
7476  * @param array  $datetime  datetime(/date)
7477  * @param string $wtz       timezone
7478  * @return int
7479  */
7480   public static function _date2timestamp( $datetime, $wtz=null ) {
7481     if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
7482     if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
7483     if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
7484     if( empty( $wtz ) && ( !isset( $datetime['tz'] ) || empty(  $datetime['tz'] )))
7485       return mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
7486     $output = $offset = 0;
7487     if( empty( $wtz )) {
7488       if( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
7489         $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) * -1;
7490         $wtz    = 'UTC';
7491       }
7492       else
7493         $wtz    = $datetime['tz'];
7494     }
7495     if(( 'Z' == $wtz ) || ( 'GMT' == strtoupper( $wtz )))
7496       $wtz      = 'UTC';
7497     try {
7498       $strdate  = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['min'], $datetime['sec'] );
7499       $d        = new DateTime( $strdate, new DateTimeZone( $wtz ));
7500       if( 0    != $offset )  // adjust for offset
7501         $d->modify( $offset.' seconds' );
7502       $output   = $d->format( 'U' );
7503       unset( $d );
7504     }
7505     catch( Exception $e ) {
7506       $output = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
7507     }
7508     return $output;
7509   }
7510 /**
7511  * ensures internal duration format for input in array format
7512  *
7513  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7514  * @since 2.14.1 - 2012-09-25
7515  * @param array $duration
7516  * @return array
7517  */
7518   public static function _duration_array( $duration ) {
7519     return iCalUtilityFunctions::_duration2arr( $duration );
7520   }
7521   public static function _duration2arr( $duration ) {
7522     $output = array();
7523     if(    is_array( $duration )        &&
+7524        ( 1 == count( $duration ))       &&
7525               isset( $duration['sec'] ) &&
+7526               ( 60 < $duration['sec'] )) {
7527       $durseconds  = $duration['sec'];
7528       $output['week'] = (int) floor( $durseconds / ( 60 * 60 * 24 * 7 ));
7529       $durseconds     =              $durseconds % ( 60 * 60 * 24 * 7 );
7530       $output['day']  = (int) floor( $durseconds / ( 60 * 60 * 24 ));
7531       $durseconds     =              $durseconds % ( 60 * 60 * 24 );
7532       $output['hour'] = (int) floor( $durseconds / ( 60 * 60 ));
7533       $durseconds     =              $durseconds % ( 60 * 60 );
7534       $output['min']  = (int) floor( $durseconds / ( 60 ));
7535       $output['sec']  =            ( $durseconds % ( 60 ));
7536     }
7537     else {
7538       foreach( $duration as $durKey => $durValue ) {
7539         if( empty( $durValue )) continue;
7540         switch ( $durKey ) {
7541           case '0': case 'week': $output['week']  = $durValue; break;
7542           case '1': case 'day':  $output['day']   = $durValue; break;
7543           case '2': case 'hour': $output['hour']  = $durValue; break;
7544           case '3': case 'min':  $output['min']   = $durValue; break;
7545           case '4': case 'sec':  $output['sec']   = $durValue; break;
7546         }
7547       }
7548     }
7549     if( isset( $output['week'] ) && ( 0 < $output['week'] )) {
7550       unset( $output['day'], $output['hour'], $output['min'], $output['sec'] );
7551       return $output;
7552     }
7553     unset( $output['week'] );
7554     if( empty( $output['day'] ))
7555       unset( $output['day'] );
7556     if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) {
7557       if( !isset( $output['hour'] )) $output['hour'] = 0;
7558       if( !isset( $output['min']  )) $output['min']  = 0;
7559       if( !isset( $output['sec']  )) $output['sec']  = 0;
7560       if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
7561         unset( $output['hour'], $output['min'], $output['sec'] );
7562     }
7563     return $output;
7564   }
7565 /**
7566  * convert startdate+duration to a array format datetime
7567  *
7568  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7569  * @since 2.15.12 - 2012-10-31
7570  * @param array   $startdate
7571  * @param array   $duration
7572  * @return array, date format
7573  */
7574   public static function _duration2date( $startdate, $duration ) {
7575     $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
7576     $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
7577     $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0;
7578     $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0;
7579     $dtend = 0;
7580     if(    isset( $duration['week'] )) $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
7581     if(    isset( $duration['day'] ))  $dtend += ( $duration['day'] * 24 * 60 * 60 );
7582     if(    isset( $duration['hour'] )) $dtend += ( $duration['hour'] * 60 *60 );
7583     if(    isset( $duration['min'] ))  $dtend += ( $duration['min'] * 60 );
7584     if(    isset( $duration['sec'] ))  $dtend +=   $duration['sec'];
7585     $date     = date( 'Y-m-d-H-i-s', mktime((int) $startdate['hour'], (int) $startdate['min'], (int) ( $startdate['sec'] + $dtend ), (int) $startdate['month'], (int) $startdate['day'], (int) $startdate['year'] ));
7586     $d        = explode( '-', $date );
7587     $dtend2   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
7588     if( isset( $startdate['tz'] ))
7589       $dtend2['tz']   = $startdate['tz'];
7590     if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
7591       unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
7592     return $dtend2;
7593   }
7594 /**
7595  * ensures internal duration format for an input string (iCal) formatted duration
7596  *
7597  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7598  * @since 2.14.1 - 2012-09-25
7599  * @param string $duration
7600  * @return array
7601  */
7602   public static function _duration_string( $duration ) {
7603     return iCalUtilityFunctions::_durationStr2arr( $duration );
7604   }
7605   public static function _durationStr2arr( $duration ) {
7606     $duration = (string) trim( $duration );
7607     while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
7608       if( 0 < strlen( $duration ))
7609         $duration = substr( $duration, 1 );
7610       else
7611         return false; // no leading P !?!?
7612     }
7613     $duration = substr( $duration, 1 ); // skip P
7614     $duration = str_replace ( 't', 'T', $duration );
7615     $duration = str_replace ( 'T', '', $duration );
7616     $output = array();
7617     $val    = null;
7618     for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
7619       switch( strtoupper( substr( $duration, $ix, 1 ))) {
7620        case 'W':
7621          $output['week'] = $val;
7622          $val            = null;
7623          break;
7624        case 'D':
7625          $output['day']  = $val;
7626          $val            = null;
7627          break;
7628        case 'H':
7629          $output['hour'] = $val;
7630          $val            = null;
7631          break;
7632        case 'M':
7633          $output['min']  = $val;
7634          $val            = null;
7635          break;
7636        case 'S':
7637          $output['sec']  = $val;
7638          $val            = null;
7639          break;
7640        default:
7641          if( !ctype_digit( substr( $duration, $ix, 1 )))
7642            return false; // unknown duration control character  !?!?
7643          else
7644            $val .= substr( $duration, $ix, 1 );
7645       }
7646     }
7647     return iCalUtilityFunctions::_duration2arr( $output );
7648   }
7649 /**
7650  * creates formatted output for calendar component property data value type duration
7651  *
7652  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7653  * @since 2.15.8 - 2012-10-30
7654  * @param array $duration, array( week, day, hour, min, sec )
7655  * @return string
7656  */
7657   public static function _format_duration( $duration ) {
7658     return iCalUtilityFunctions::_duration2str( $duration );
7659   }
7660   public static function _duration2str( $duration ) {
7661     if( isset( $duration['week'] ) ||
7662         isset( $duration['day'] )  ||
7663         isset( $duration['hour'] ) ||
7664         isset( $duration['min'] )  ||
7665         isset( $duration['sec'] ))
7666        $ok = TRUE;
7667     else
7668       return;
7669     if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
7670       return 'P'.$duration['week'].'W';
7671     $output = 'P';
7672     if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
7673       $output .= $duration['day'].'D';
7674     if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
+7675        ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ||
+7676        ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))) {
7677       $output .= 'T';
7678       $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H';
7679       $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '0M';
7680       $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '0S';
7681     }
7682     if( 'P' == $output )
7683       $output = 'PT0H0M0S';
7684     return $output;
7685   }
7686 /**
7687  * removes expkey+expvalue from array and returns hitval (if found) else returns elseval
7688  *
7689  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7690  * @since 2.4.16 - 2008-11-08
7691  * @param array $array
7692  * @param string $expkey, expected key
7693  * @param string $expval, expected value
7694  * @param int $hitVal optional, return value if found
7695  * @param int $elseVal optional, return value if not found
7696  * @param int $preSet optional, return value if already preset
7697  * @return int
7698  */
7699   public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
7700     if( $preSet )
7701       return $preSet;
7702     if( !is_array( $array ) || ( 0 == count( $array )))
7703       return $elseVal;
7704     foreach( $array as $key => $value ) {
7705       if( strtoupper( $expkey ) == strtoupper( $key )) {
7706         if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
7707           unset( $array[$key] );
7708           return $hitVal;
7709         }
7710       }
7711     }
7712     return $elseVal;
7713   }
7714 /**
7715  * checks if input contains a (array formatted) date/time
7716  *
7717  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7718  * @since 2.11.8 - 2012-01-20
7719  * @param array $input
7720  * @return bool
7721  */
7722   public static function _isArrayDate( $input ) {
7723     if( !is_array( $input ))
7724       return FALSE;
7725     if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 ))))
7726       return FALSE;
7727     if( 7 == count( $input ))
7728       return TRUE;
7729     if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
7730       return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
7731     if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
7732       return FALSE;
7733     if( in_array( 0, $input ))
7734       return FALSE;
7735     if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
7736       return FALSE;
7737     if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
+7738          checkdate( (int) $input[1], (int) $input[2], (int) $input[0] ))
7739       return TRUE;
7740     $input = iCalUtilityFunctions::_strdate2date( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y
7741     if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
7742       return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
7743     return FALSE;
7744   }
7745 /**
7746  * checks if input array contains a timestamp date
7747  *
7748  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7749  * @since 2.4.16 - 2008-10-18
7750  * @param array $input
7751  * @return bool
7752  */
7753   public static function _isArrayTimestampDate( $input ) {
7754     return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
7755   }
7756 /**
7757  * controls if input string contains (trailing) UTC/iCal offset
7758  *
7759  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7760  * @since 2.14.1 - 2012-09-21
7761  * @param string $input
7762  * @return bool
7763  */
7764   public static function _isOffset( $input ) {
7765     $input         = trim( (string) $input );
7766     if( 'Z' == substr( $input, -1 ))
7767       return TRUE;
7768     elseif((   5 <= strlen( $input )) &&
+7769        ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
+7770        (   '0000' <= substr( $input, -4 )) && (   '9999' >= substr( $input, -4 )))
7771       return TRUE;
7772     elseif((    7 <= strlen( $input )) &&
+7773        ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
+7774        ( '000000' <= substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
7775       return TRUE;
7776     return FALSE;
7777   }
7778 /**
7779  * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
7780  * matching (MS) UCT offset and time zone descriptors
7781  *
7782  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7783  * @since 2.14.1 - 2012-09-16
7784  * @param string $timezone, input/output variable reference
7785  * @return bool
7786  */
7787   public static function ms2phpTZ( & $timezone ) {
7788     if( empty( $timezone ))
7789       return FALSE;
7790     $search = str_replace( '"', '', $timezone );
7791     $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
7792     if( '(UTC' != substr( $search, 0, 4 ))
7793       return FALSE;
7794     if( FALSE === ( $pos = strpos( $search, ')' )))
7795       return FALSE;
7796     $pos    = strpos( $search, ')' );
7797     $searchOffset = substr( $search, 4, ( $pos - 4 ));
7798     $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
7799     while( ' ' ==substr( $search, ( $pos + 1 )))
7800       $pos += 1;
7801     $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) ));
7802     $searchWords  = explode( ' ', $searchText );
7803     $timezone_abbreviations = DateTimeZone::listAbbreviations();
7804     $hits = array();
7805     foreach( $timezone_abbreviations as $name => $transitions ) {
7806       foreach( $transitions as $cnt => $transition ) {
7807         if( empty( $transition['offset'] )      ||
7808             empty( $transition['timezone_id'] ) ||
+7809           ( $transition['offset'] != $searchOffset ))
7810         continue;
7811         $cWords = explode( '/', $transition['timezone_id'] );
7812         $cPrio   = $hitCnt = $rank = 0;
7813         foreach( $cWords as $cWord ) {
7814           if( empty( $cWord ))
7815             continue;
7816           $cPrio += 1;
7817           $sPrio  = 0;
7818           foreach( $searchWords as $sWord ) {
7819             if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
7820               continue;
7821             $sPrio += 1;
7822             if( strtolower( $cWord ) == strtolower( $sWord )) {
7823               $hitCnt += 1;
7824               $rank   += ( $cPrio + $sPrio );
7825             }
7826             else
7827               $rank += 10;
7828           }
7829         }
7830         if( 0 < $hitCnt ) {
7831           $hits[$rank][] = $transition['timezone_id'];
7832         }
7833       }
7834     }
7835     unset( $timezone_abbreviations );
7836     if( empty( $hits ))
7837       return FALSE;
7838     ksort( $hits );
7839     foreach( $hits as $rank => $tzs ) {
7840       if( !empty( $tzs )) {
7841         $timezone = reset( $tzs );
7842         return TRUE;
7843       }
7844     }
7845     return FALSE;
7846   }
7847 /**
7848  * transforms offset in seconds to [-/+]hhmm[ss]
7849  *
7850  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7851  * @since 2011-05-02
7852  * @param string $seconds
7853  * @return string
7854  */
7855   public static function offsetSec2His( $seconds ) {
7856     if( '-' == substr( $seconds, 0, 1 )) {
7857       $prefix  = '-';
7858       $seconds = substr( $seconds, 1 );
7859     }
7860     elseif( '+' == substr( $seconds, 0, 1 )) {
7861       $prefix  = '+';
7862       $seconds = substr( $seconds, 1 );
7863     }
7864     else
7865       $prefix  = '+';
7866     $output  = '';
7867     $hour    = (int) floor( $seconds / 3600 );
7868     if( 10 > $hour )
7869       $hour  = '0'.$hour;
7870     $seconds = $seconds % 3600;
7871     $min     = (int) floor( $seconds / 60 );
7872     if( 10 > $min )
7873       $min   = '0'.$min;
7874     $output  = $hour.$min;
7875     $seconds = $seconds % 60;
7876     if( 0 < $seconds) {
7877       if( 9 < $seconds)
7878         $output .= $seconds;
7879       else
7880         $output .= '0'.$seconds;
7881     }
7882     return $prefix.$output;
7883   }
7884 /**
7885  * updates an array with dates based on a recur pattern
7886  *
7887  * if missing, UNTIL is set 1 year from startdate (emergency break)
7888  *
7889  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7890  * @since 2.10.19 - 2011-10-31
7891  * @param array $result, array to update, array([timestamp] => timestamp)
7892  * @param array $recur, pattern for recurrency (only value part, params ignored)
7893  * @param array $wdate, component start date
7894  * @param array $startdate, start date
7895  * @param array $enddate, optional
7896  * @return void
7897  * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start
7898  */
7899   public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) {
7900     foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
7901     $wdateStart  = $wdate;
7902     $wdatets     = iCalUtilityFunctions::_date2timestamp( $wdate );
7903     $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate );
7904     if( !$enddate ) {
7905       $enddate = $startdate;
7906       $enddate['year'] += 1;
7907     }
7908 // echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br />\n";print_r($recur);echo "<br />\n";//test###
7909     $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break
7910     if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
7911       $recur['UNTIL'] = $enddate; // create break
7912     if( isset( $recur['UNTIL'] )) {
7913       $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] );
7914       if( $endDatets > $tdatets ) {
7915         $endDatets = $tdatets; // emergency break
7916         $enddate   = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
7917       }
7918       else
7919         $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
7920     }
7921     if( $wdatets > $endDatets ) {
7922 // echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
7923       return array(); // nothing to do.. .
7924     }
7925     if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
7926       $recur['FREQ'] = 'DAILY'; // ??
7927     $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
7928     $weekStart = (int) date( 'W', ( $wdatets + $wkst ));
7929     if( !isset( $recur['INTERVAL'] ))
7930       $recur['INTERVAL'] = 1;
7931     $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
7932             /* find out how to step up dates and set index for interval count */
7933     $step = array();
7934     if( 'YEARLY' == $recur['FREQ'] )
7935       $step['year']  = 1;
7936     elseif( 'MONTHLY' == $recur['FREQ'] )
7937       $step['month'] = 1;
7938     elseif( 'WEEKLY' == $recur['FREQ'] )
7939       $step['day']   = 7;
7940     else
7941       $step['day']   = 1;
7942     if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
7943       $step = array( 'month' => 1 );
7944     if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
7945       $step = array( 'day' => 7 );
7946     if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
7947       $step = array( 'day' => 1 );
7948     $intervalarr = array();
7949     if( 1 < $recur['INTERVAL'] ) {
7950       $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
7951       $intervalarr = array( $intervalix => 0 );
7952     }
7953     if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
7954       $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
7955 // echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br />\n"; // test ###
7956       if( is_array( $recur['BYSETPOS'] )) {
7957         foreach( $recur['BYSETPOS'] as $bix => $bval )
7958           $recur['BYSETPOS'][$bix] = (int) $bval;
7959       }
7960       else
7961         $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
7962       if( 'YEARLY' == $recur['FREQ'] ) {
7963         $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
7964         $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
7965         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year
7966       }
7967       elseif( 'MONTHLY' == $recur['FREQ'] ) {
7968         $wdate['day']   = 1; // start from beginning of month
7969         $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
7970         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month
7971       }
7972       else
7973         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period
7974 // echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br />\n";//test###
7975       $bysetposWold = (int) date( 'W', ( $wdatets + $wkst ));
7976       $bysetposYold = $wdate['year'];
7977       $bysetposMold = $wdate['month'];
7978       $bysetposDold = $wdate['day'];
7979     }
7980     else
7981       iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
7982     $year_old     = null;
7983     $daynames     = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
7984              /* MAIN LOOP */
7985 // echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br />\n";//test
7986     while( TRUE ) {
7987       if( isset( $endDatets ) && ( $wdatets > $endDatets ))
7988         break;
7989       if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
7990         break;
7991       if( $year_old != $wdate['year'] ) {
7992         $year_old   = $wdate['year'];
7993         $daycnts    = array();
7994         $yeardays   = $weekno = 0;
7995         $yeardaycnt = array();
7996         foreach( $daynames as $dn )
7997           $yeardaycnt[$dn] = 0;
7998         for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
7999           $daycnts[$m] = array();
8000           $weekdaycnt = array();
8001           foreach( $daynames as $dn )
8002             $weekdaycnt[$dn] = 0;
8003           $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
8004           for( $d   = 1; $d <= $mcnt; $d++ ) {
8005             $daycnts[$m][$d] = array();
8006             if( isset( $recur['BYYEARDAY'] )) {
8007               $yeardays++;
8008               $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
8009             }
8010             if( isset( $recur['BYDAY'] )) {
8011               $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
8012               $day    = $daynames[$day];
8013               $daycnts[$m][$d]['DAY'] = $day;
8014               $weekdaycnt[$day]++;
8015               $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
8016               $yeardaycnt[$day]++;
8017               $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
8018             }
8019             if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
8020               $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
8021           }
8022         }
8023         $daycnt = 0;
8024         $yeardaycnt = array();
8025         if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
8026           $weekno = null;
8027           for( $d=31; $d > 25; $d-- ) { // get last weekno for year
8028             if( !$weekno )
8029               $weekno = $daycnts[12][$d]['weekno_up'];
8030             elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
8031               $weekno = $daycnts[12][$d]['weekno_up'];
8032               break;
8033             }
8034           }
8035         }
8036         for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
8037           $weekdaycnt = array();
8038           foreach( $daynames as $dn )
8039             $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
8040           $monthcnt = 0;
8041           $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
8042           for( $d   = $mcnt; $d > 0; $d-- ) {
8043             if( isset( $recur['BYYEARDAY'] )) {
8044               $daycnt -= 1;
8045               $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
8046             }
8047             if( isset( $recur['BYMONTHDAY'] )) {
8048               $monthcnt -= 1;
8049               $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
8050             }
8051             if( isset( $recur['BYDAY'] )) {
8052               $day  = $daycnts[$m][$d]['DAY'];
8053               $weekdaycnt[$day] -= 1;
8054               $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
8055               $yeardaycnt[$day] -= 1;
8056               $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
8057             }
8058             if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
8059               $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
8060           }
8061         }
8062       }
8063             /* check interval */
8064       if( 1 < $recur['INTERVAL'] ) {
8065             /* create interval index */
8066         $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
8067             /* check interval */
8068         $currentKey = array_keys( $intervalarr );
8069         $currentKey = end( $currentKey ); // get last index
8070         if( $currentKey != $intervalix )
8071           $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
8072         if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
+8073            ( 0 != $intervalarr[$intervalix] )) {
8074             /* step up date */
8075 // echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
8076           iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
8077           continue;
8078         }
8079         else // continue within the selected interval
8080           $intervalarr[$intervalix] = 0;
8081 // echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
8082       }
8083       $updateOK = TRUE;
8084       if( $updateOK && isset( $recur['BYMONTH'] ))
8085         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
+8086                                            , $wdate['month']
+8087                                            ,($wdate['month'] - 13));
8088       if( $updateOK && isset( $recur['BYWEEKNO'] ))
8089         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
+8090                                            , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
+8091                                            , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
8092       if( $updateOK && isset( $recur['BYYEARDAY'] ))
8093         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
+8094                                            , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
+8095                                            , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
8096       if( $updateOK && isset( $recur['BYMONTHDAY'] ))
8097         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
+8098                                            , $wdate['day']
+8099                                            , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
8100 // echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n";//test###
8101       if( $updateOK && isset( $recur['BYDAY'] )) {
8102         $updateOK = FALSE;
8103         $m = $wdate['month'];
8104         $d = $wdate['day'];
8105         if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
8106           $daynoexists = $daynosw = $daynamesw =  FALSE;
8107           if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
8108             $daynamesw = TRUE;
8109           if( isset( $recur['BYDAY'][0] )) {
8110             $daynoexists = TRUE;
8111             if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
8112               $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
+8113                                                 , $daycnts[$m][$d]['monthdayno_up']
+8114                                                 , $daycnts[$m][$d]['monthdayno_down'] );
8115             elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
8116               $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
+8117                                                 , $daycnts[$m][$d]['yeardayno_up']
+8118                                                 , $daycnts[$m][$d]['yeardayno_down'] );
8119           }
8120           if((  $daynoexists &&  $daynosw && $daynamesw ) ||
+8121              ( !$daynoexists && !$daynosw && $daynamesw )) {
8122             $updateOK = TRUE;
8123 // echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
8124           }
8125 // echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
8126         }
8127         else {
8128           foreach( $recur['BYDAY'] as $bydayvalue ) {
8129             $daynoexists = $daynosw = $daynamesw = FALSE;
8130             if( isset( $bydayvalue['DAY'] ) &&
+8131                      ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
8132               $daynamesw = TRUE;
8133             if( isset( $bydayvalue[0] )) {
8134               $daynoexists = TRUE;
8135               if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
8136                    isset( $recur['BYMONTH'] ))
8137                 $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
+8138                                                   , $daycnts[$m][$d]['monthdayno_up']
+8139                                                   , $daycnts[$m][$d]['monthdayno_down'] );
8140               elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
8141                 $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
+8142                                                   , $daycnts[$m][$d]['yeardayno_up']
+8143                                                   , $daycnts[$m][$d]['yeardayno_down'] );
8144             }
8145 // echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br />\n"; // test ###
8146             if((  $daynoexists &&  $daynosw && $daynamesw ) ||
+8147                ( !$daynoexists && !$daynosw && $daynamesw )) {
8148               $updateOK = TRUE;
8149               break;
8150             }
8151           }
8152         }
8153       }
8154 // echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n"; // test ###
8155             /* check BYSETPOS */
8156       if( $updateOK ) {
8157         if( isset( $recur['BYSETPOS'] ) &&
+8158           ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
8159           if( isset( $recur['WEEKLY'] )) {
8160             if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
8161               $bysetposw1[] = $wdatets;
8162             else
8163               $bysetposw2[] = $wdatets;
8164           }
8165           else {
8166             if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  &&
+8167                                             ( $bysetposYold == $wdate['year'] ))   ||
+8168                ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  &&
+8169                                            (( $bysetposYold == $wdate['year'] )  &&
+8170                                             ( $bysetposMold == $wdate['month'] ))) ||
+8171                ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  &&
+8172                                            (( $bysetposYold == $wdate['year'] )  &&
+8173                                             ( $bysetposMold == $wdate['month'])  &&
+8174                                             ( $bysetposDold == $wdate['day'] )))) {
8175 // echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
8176               $bysetposymd1[] = $wdatets;
8177             }
8178             else {
8179 // echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
8180               $bysetposymd2[] = $wdatets;
8181             }
8182           }
8183         }
8184         else {
8185             /* update result array if BYSETPOS is set */
8186           $countcnt++;
8187           if( $startdatets <= $wdatets ) { // only output within period
8188             $result[$wdatets] = TRUE;
8189 // echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
8190           }
8191 // echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br />\n";//test
8192           $updateOK = FALSE;
8193         }
8194       }
8195             /* step up date */
8196       iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
8197             /* check if BYSETPOS is set for updating result array */
8198       if( $updateOK && isset( $recur['BYSETPOS'] )) {
8199         $bysetpos       = FALSE;
8200         if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) &&
+8201           ( $bysetposYold != $wdate['year'] )) {
8202           $bysetpos     = TRUE;
8203           $bysetposYold = $wdate['year'];
8204         }
8205         elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
+8206          (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
8207           $bysetpos     = TRUE;
8208           $bysetposYold = $wdate['year'];
8209           $bysetposMold = $wdate['month'];
8210         }
8211         elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) {
8212           $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
8213           if( $bysetposWold != $weekno ) {
8214             $bysetposWold = $weekno;
8215             $bysetpos     = TRUE;
8216           }
8217         }
8218         elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) &&
+8219          (( $bysetposYold != $wdate['year'] )  ||
+8220           ( $bysetposMold != $wdate['month'] ) ||
+8221           ( $bysetposDold != $wdate['day'] ))) {
8222           $bysetpos     = TRUE;
8223           $bysetposYold = $wdate['year'];
8224           $bysetposMold = $wdate['month'];
8225           $bysetposDold = $wdate['day'];
8226         }
8227         if( $bysetpos ) {
8228           if( isset( $recur['BYWEEKNO'] )) {
8229             $bysetposarr1 = & $bysetposw1;
8230             $bysetposarr2 = & $bysetposw2;
8231           }
8232           else {
8233             $bysetposarr1 = & $bysetposymd1;
8234             $bysetposarr2 = & $bysetposymd2;
8235           }
8236 // echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ###
8237           foreach( $recur['BYSETPOS'] as $ix ) {
8238             if( 0 > $ix ) // both positive and negative BYSETPOS allowed
8239               $ix = ( count( $bysetposarr1 ) + $ix + 1);
8240             $ix--;
8241             if( isset( $bysetposarr1[$ix] )) {
8242               if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period
8243 //                $testdate   = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 );                // test ###
8244 //                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ###
8245 // echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)";   // test ###
8246                 $result[$bysetposarr1[$ix]] = TRUE;
8247 // echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ###
8248               }
8249               $countcnt++;
8250             }
8251             if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
8252               break;
8253           }
8254 // echo "<br />\n"; // test ###
8255           $bysetposarr1 = $bysetposarr2;
8256           $bysetposarr2 = array();
8257         }
8258       }
8259     }
8260   }
8261   public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
8262     if( is_array( $BYvalue ) &&
+8263       ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
8264       return TRUE;
8265     elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
8266       return TRUE;
8267     else
8268       return FALSE;
8269   }
8270   public static function _recurIntervalIx( $freq, $date, $wkst ) {
8271             /* create interval index */
8272     switch( $freq ) {
8273       case 'YEARLY':
8274         $intervalix = $date['year'];
8275         break;
8276       case 'MONTHLY':
8277         $intervalix = $date['year'].'-'.$date['month'];
8278         break;
8279       case 'WEEKLY':
8280         $wdatets    = iCalUtilityFunctions::_date2timestamp( $date );
8281         $intervalix = (int) date( 'W', ( $wdatets + $wkst ));
8282        break;
8283       case 'DAILY':
8284            default:
8285         $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
8286         break;
8287     }
8288     return $intervalix;
8289   }
8290 /**
8291  * convert input format for exrule and rrule to internal format
8292  *
8293  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8294  * @since 2.14.1 - 2012-09-24
8295  * @param array $rexrule
8296  * @return array
8297  */
8298   public static function _setRexrule( $rexrule ) {
8299     $input          = array();
8300     if( empty( $rexrule ))
8301       return $input;
8302     foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
8303       $rexrulelabel = strtoupper( $rexrulelabel );
8304       if( 'UNTIL'  != $rexrulelabel )
8305         $input[$rexrulelabel]   = $rexrulevalue;
8306       else {
8307         iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
8308         if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time UTC
8309           $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 7, 'UTC' );
8310         elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or UTC date-time
8311           $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 7 : 3;
8312           $d = iCalUtilityFunctions::_chkDateArr( $rexrulevalue, $parno );
8313           if(( 3 < $parno ) && isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
8314             $strdate              = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8315             $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8316             unset( $input[$rexrulelabel]['unparsedtext'] );
8317           }
8318           else
8319            $input[$rexrulelabel] = $d;
8320         }
8321         elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC
8322           $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $rexrulevalue );
8323           unset( $input['$rexrulelabel']['unparsedtext'] );
8324         }
8325         if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
8326           $input[$rexrulelabel]['tz'] = 'Z';
8327       }
8328     }
8329             /* set recurrence rule specification in rfc2445 order */
8330     $input2 = array();
8331     if( isset( $input['FREQ'] ))
8332       $input2['FREQ']       = $input['FREQ'];
8333     if( isset( $input['UNTIL'] ))
8334       $input2['UNTIL']      = $input['UNTIL'];
8335     elseif( isset( $input['COUNT'] ))
8336       $input2['COUNT']      = $input['COUNT'];
8337     if( isset( $input['INTERVAL'] ))
8338       $input2['INTERVAL']   = $input['INTERVAL'];
8339     if( isset( $input['BYSECOND'] ))
8340       $input2['BYSECOND']   = $input['BYSECOND'];
8341     if( isset( $input['BYMINUTE'] ))
8342       $input2['BYMINUTE']   = $input['BYMINUTE'];
8343     if( isset( $input['BYHOUR'] ))
8344       $input2['BYHOUR']     = $input['BYHOUR'];
8345     if( isset( $input['BYDAY'] )) {
8346       if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
8347         $input2['BYDAY']    = strtoupper( $input['BYDAY'] );
8348       else {
8349         foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
8350           if( 'DAY'        == strtoupper( $BYDAYx ))
8351              $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
8352           elseif( !is_array( $BYDAYv )) {
8353              $input2['BYDAY'][$BYDAYx]  = $BYDAYv;
8354           }
8355           else {
8356             foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
8357               if( 'DAY'    == strtoupper( $BYDAYx2 ))
8358                  $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
8359               else
8360                  $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
8361             }
8362           }
8363         }
8364       }
8365     }
8366     if( isset( $input['BYMONTHDAY'] ))
8367       $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
8368     if( isset( $input['BYYEARDAY'] ))
8369       $input2['BYYEARDAY']  = $input['BYYEARDAY'];
8370     if( isset( $input['BYWEEKNO'] ))
8371       $input2['BYWEEKNO']   = $input['BYWEEKNO'];
8372     if( isset( $input['BYMONTH'] ))
8373       $input2['BYMONTH']    = $input['BYMONTH'];
8374     if( isset( $input['BYSETPOS'] ))
8375       $input2['BYSETPOS']   = $input['BYSETPOS'];
8376     if( isset( $input['WKST'] ))
8377       $input2['WKST']       = $input['WKST'];
8378     return $input2;
8379   }
8380 /**
8381  * convert format for input date to internal date with parameters
8382  *
8383  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8384  * @since 2.14.1 - 2012-10-15
8385  * @param mixed  $year
8386  * @param mixed  $month   optional
8387  * @param int    $day     optional
8388  * @param int    $hour    optional
8389  * @param int    $min     optional
8390  * @param int    $sec     optional
8391  * @param string $tz      optional
8392  * @param array  $params  optional
8393  * @param string $caller  optional
8394  * @param string $objName optional
8395  * @param string $tzid    optional
8396  * @return array
8397  */
8398   public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) {
8399     $input = $parno = null;
8400     $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
8401     iCalUtilityFunctions::_strDate2arr( $year );
8402     if( iCalUtilityFunctions::_isArrayDate( $year )) {
8403       $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, $parno );
8404       if( 100 > $input['value']['year'] )
8405         $input['value']['year'] += 2000;
8406       if( $localtime )
8407         unset( $month['VALUE'], $month['TZID'] );
8408       elseif( !isset( $month['TZID'] ) && isset( $tzid ))
8409         $month['TZID'] = $tzid;
8410       if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
8411         unset( $month['TZID'] );
8412       elseif( isset( $month['TZID'] ) && iCalUtilityFunctions::_isOffset( $month['TZID'] )) {
8413         $input['value']['tz'] = $month['TZID'];
8414         unset( $month['TZID'] );
8415       }
8416       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8417       $hitval          = ( isset( $input['value']['tz'] )) ? 7 : 6;
8418       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
8419       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $input['value'] ), $parno );
8420       if(( 3 != $parno ) && isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8421         $d             = $input['value'];
8422         $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8423         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
8424         unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8425       }
8426       if( isset( $input['value']['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8427         $input['params']['TZID'] = $input['value']['tz'];
8428         unset( $input['value']['tz'] );
8429       }
8430     } // end if( iCalUtilityFunctions::_isArrayDate( $year ))
8431     elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
8432       if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
8433       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8434       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
8435       $hitval          = 7;
8436       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
8437       if( !isset( $input['params']['TZID'] ) && !empty( $tzid ))
8438         $input['params']['TZID'] = $tzid;
8439       if( isset( $year['tz'] )) {
8440         $parno         = 6;
8441         if( !iCalUtilityFunctions::_isOffset( $year['tz'] ))
8442           $input['params']['TZID'] = $year['tz'];
8443       }
8444       elseif( isset( $input['params']['TZID'] )) {
8445         $year['tz']    = $input['params']['TZID'];
8446         $parno         = 6;
8447         if( iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8448           unset( $input['params']['TZID'] );
8449           $parno       = 7;
8450         }
8451       }
8452       $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno );
8453     } // end elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year ))
8454     elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone]
8455       if( $localtime )
8456         unset( $month['VALUE'], $month['TZID'] );
8457       elseif( !isset( $month['TZID'] ) && !empty( $tzid ))
8458         $month['TZID'] = $tzid;
8459       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8460       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
8461       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
8462       $input['value']  = iCalUtilityFunctions::_strdate2date( $year, $parno );
8463       unset( $input['value']['unparsedtext'] );
8464       if( isset( $input['value']['tz'] )) {
8465         if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8466           $d           = $input['value'];
8467           $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8468           $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8469           unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8470         }
8471         else {
8472           $input['params']['TZID'] = $input['value']['tz'];
8473           unset( $input['value']['tz'] );
8474         }
8475       }
8476       elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8477         $d             = $input['value'];
8478         $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
8479         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8480         unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8481       }
8482     } // end elseif( 8 <= strlen( trim( $year )))
8483     else {
8484       if( is_array( $params ))
8485         $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
8486       elseif( is_array( $tz )) {
8487         $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' ));
8488         $tz = FALSE;
8489       }
8490       elseif( is_array( $hour )) {
8491         $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' ));
8492         $hour = $min = $sec = $tz = FALSE;
8493       }
8494       if( $localtime )
8495         unset ( $input['params']['VALUE'], $input['params']['TZID'] );
8496       elseif( !isset( $tz ) && !isset( $input['params']['TZID'] ) && !empty( $tzid ))
8497         $input['params']['TZID'] = $tzid;
8498       elseif( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz ))
8499         unset( $input['params']['TZID'] );
8500       elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8501         $tz            = $input['params']['TZID'];
8502         unset( $input['params']['TZID'] );
8503       }
8504       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
8505       $hitval          = ( iCalUtilityFunctions::_isOffset( $tz )) ? 7 : 6;
8506       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
8507       $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day );
8508       if( 3 != $parno ) {
8509         $input['value']['hour'] = ( $hour ) ? $hour : '0';
8510         $input['value']['min']  = ( $min )  ? $min  : '0';
8511         $input['value']['sec']  = ( $sec )  ? $sec  : '0';
8512         if( !empty( $tz ))
8513           $input['value']['tz'] = $tz;
8514         $strdate       = iCalUtilityFunctions::_date2strdate( $input['value'], $parno );
8515         if( !empty( $tz ) && !iCalUtilityFunctions::_isOffset( $tz ))
8516           $strdate    .= ( 'Z' == $tz ) ? $tz : ' '.$tz;
8517         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
8518         unset( $input['value']['unparsedtext'] );
8519         if( isset( $input['value']['tz'] )) {
8520           if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8521             $d           = $input['value'];
8522             $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8523             $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8524             unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8525           }
8526           else {
8527             $input['params']['TZID'] = $input['value']['tz'];
8528             unset( $input['value']['tz'] );
8529           }
8530         }
8531         elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8532           $d             = $input['value'];
8533           $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
8534           $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8535           unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8536         }
8537       }
8538     } // end else (i.e. using all arguments)
8539     if(( 3 == $parno ) || ( isset( $input['params']['VALUE'] ) && ( 'DATE' == $input['params']['VALUE'] ))) {
8540       $input['params']['VALUE'] = 'DATE';
8541       unset( $input['value']['hour'], $input['value']['min'], $input['value']['sec'], $input['value']['tz'], $input['params']['TZID'] );
8542     }
8543     elseif( isset( $input['params']['TZID'] )) {
8544       if(( 'UTC' == strtoupper( $input['params']['TZID'] )) || ( 'GMT' == strtoupper( $input['params']['TZID'] ))) {
8545         $input['value']['tz'] = 'Z';
8546         unset( $input['params']['TZID'] );
8547       }
8548       else
8549         unset( $input['value']['tz'] );
8550     }
8551     elseif( isset( $input['value']['tz'] )) {
8552       if(( 'UTC' == strtoupper( $input['value']['tz'] )) || ( 'GMT' == strtoupper( $input['value']['tz'] )))
8553         $input['value']['tz'] = 'Z';
8554       if( 'Z' != $input['value']['tz'] ) {
8555         $input['params']['TZID'] = $input['value']['tz'];
8556         unset( $input['value']['tz'] );
8557       }
8558       else
8559         unset( $input['params']['TZID'] );
8560     }
8561     if( $localtime )
8562       unset( $input['value']['tz'], $input['params']['TZID'] );
8563     return $input;
8564   }
8565 /**
8566  * convert format for input date (UTC) to internal date with parameters
8567  *
8568  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8569  * @since 2.14.4 - 2012-10-06
8570  * @param mixed $year
8571  * @param mixed $month  optional
8572  * @param int   $day    optional
8573  * @param int   $hour   optional
8574  * @param int   $min    optional
8575  * @param int   $sec    optional
8576  * @param array $params optional
8577  * @return array
8578  */
8579   public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
8580     $input = null;
8581     iCalUtilityFunctions::_strDate2arr( $year );
8582     if( iCalUtilityFunctions::_isArrayDate( $year )) {
8583       $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, 7 );
8584       if( isset( $input['value']['year'] ) && ( 100 > $input['value']['year'] ))
8585         $input['value']['year'] += 2000;
8586       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8587       if( isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8588         $d             = $input['value'];
8589         $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8590         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8591         unset( $input['value']['unparsedtext'] );
8592       }
8593     }
8594     elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
8595       $year['tz']      = 'UTC';
8596       $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 );
8597       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8598     }
8599     elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
8600       $input['value']  = iCalUtilityFunctions::_strdate2date( $year, 7 );
8601       unset( $input['value']['unparsedtext'] );
8602       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8603     }
8604     else {
8605       $input['value']  = array( 'year'  => $year
+8606                               , 'month' => $month
+8607                               , 'day'   => $day
+8608                               , 'hour'  => $hour
+8609                               , 'min'   => $min
+8610                               , 'sec'   => $sec );
8611       if(  isset( $tz )) $input['value']['tz'] = $tz;
8612       if(( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) ||
+8613          ( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))) {
8614           if( !isset( $tz ) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
8615             $input['value']['tz'] = $input['params']['TZID'];
8616           unset( $input['params']['TZID'] );
8617         $strdate        = iCalUtilityFunctions::_date2strdate( $input['value'], 7 );
8618         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8619         unset( $input['value']['unparsedtext'] );
8620       }
8621       $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
8622     }
8623     $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
8624     if( !isset( $input['value']['hour'] )) $input['value']['hour'] = 0;
8625     if( !isset( $input['value']['min'] ))  $input['value']['min']  = 0;
8626     if( !isset( $input['value']['sec'] ))  $input['value']['sec']  = 0;
8627     $input['value']['tz'] = 'Z';
8628     return $input;
8629   }
8630 /**
8631  * check index and set (an indexed) content in multiple value array
8632  *
8633  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8634  * @since 2.6.12 - 2011-01-03
8635  * @param array $valArr
8636  * @param mixed $value
8637  * @param array $params
8638  * @param array $defaults
8639  * @param int $index
8640  * @return void
8641  */
8642   public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
8643     if( !is_array( $valArr )) $valArr = array();
8644     if( $index )
8645       $index = $index - 1;
8646     elseif( 0 < count( $valArr )) {
8647       $keys  = array_keys( $valArr );
8648       $index = end( $keys ) + 1;
8649     }
8650     else
8651       $index = 0;
8652     $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
8653     ksort( $valArr );
8654   }
8655 /**
8656  * set input (formatted) parameters- component property attributes
8657  *
8658  * default parameters can be set, if missing
8659  *
8660  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8661  * @since 1.x.x - 2007-05-01
8662  * @param array $params
8663  * @param array $defaults
8664  * @return array
8665  */
8666   public static function _setParams( $params, $defaults=FALSE ) {
8667     if( !is_array( $params))
8668       $params = array();
8669     $input = array();
8670     foreach( $params as $paramKey => $paramValue ) {
8671       if( is_array( $paramValue )) {
8672         foreach( $paramValue as $pkey => $pValue ) {
8673           if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
8674             $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
8675         }
8676       }
8677       elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
8678         $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
8679       if( 'VALUE' == strtoupper( $paramKey ))
8680         $input['VALUE']                 = strtoupper( $paramValue );
8681       else
8682         $input[strtoupper( $paramKey )] = $paramValue;
8683     }
8684     if( is_array( $defaults )) {
8685       foreach( $defaults as $paramKey => $paramValue ) {
8686         if( !isset( $input[$paramKey] ))
8687           $input[$paramKey] = $paramValue;
8688       }
8689     }
8690     return (0 < count( $input )) ? $input : null;
8691   }
8692 /**
8693  * break lines at pos 75
8694  *
8695  * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
8696  * break. Long content lines SHOULD be split into a multiple line
8697  * representations using a line "folding" technique. That is, a long
8698  * line can be split between any two characters by inserting a CRLF
8699  * immediately followed by a single linear white space character (i.e.,
8700  * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
8701  * of CRLF followed immediately by a single linear white space character
8702  * is ignored (i.e., removed) when processing the content type.
8703  *
8704  * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
8705  * the reserved expression "\n" in the arg $string could be broken up by the
8706  * folding of lines, causing ambiguity in the return string.
8707  *
8708  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8709  * @since 2.16.2 - 2012-12-18
8710  * @param string $value
8711  * @return string
8712  */
8713   public static function _size75( $string, $nl ) {
8714     $tmp             = $string;
8715     $string          = '';
8716     $cCnt = $x       = 0;
8717     while( TRUE ) {
8718       if( !isset( $tmp[$x] )) {
8719         $string     .= $nl;                           // loop breakes here
8720         break;
8721       }
8722       elseif(( 74   <= $cCnt ) && ( '\\'  == $tmp[$x] ) && ( 'n' == $tmp[$x+1] )) {
8723         $string     .= $nl.' \n';                     // don't break lines inside '\n'
8724         $x          += 2;
8725         if( !isset( $tmp[$x] )) {
8726           $string   .= $nl;
8727           break;
8728         }
8729         $cCnt        = 3;
8730       }
8731       elseif( 75    <= $cCnt ) {
8732         $string     .= $nl.' ';
8733         $cCnt        = 1;
8734       }
8735       $byte          = ord( $tmp[$x] );
8736       $string       .= $tmp[$x];
8737       switch( TRUE ) { // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
8738         case(( $byte >= 0x20 ) && ( $byte <= 0x7F )): // characters U-00000000 - U-0000007F (same as ASCII)
8739           $cCnt     += 1;
8740           break;                                      // add a one byte character
8741         case(( $byte & 0xE0) == 0xC0 ):               // characters U-00000080 - U-000007FF, mask 110XXXXX
8742           if( isset( $tmp[$x+1] )) {
8743             $cCnt   += 1;
8744             $string  .= $tmp[$x+1];
8745             $x       += 1;                            // add a two bytes character
8746           }
8747           break;
8748         case(( $byte & 0xF0 ) == 0xE0 ):              // characters U-00000800 - U-0000FFFF, mask 1110XXXX
8749           if( isset( $tmp[$x+2] )) {
8750             $cCnt   += 1;
8751             $string .= $tmp[$x+1].$tmp[$x+2];
8752             $x      += 2;                             // add a three bytes character
8753           }
8754           break;
8755         case(( $byte & 0xF8 ) == 0xF0 ):              // characters U-00010000 - U-001FFFFF, mask 11110XXX
8756           if( isset( $tmp[$x+3] )) {
8757             $cCnt   += 1;
8758             $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3];
8759             $x      += 3;                             // add a four bytes character
8760           }
8761           break;
8762         case(( $byte & 0xFC ) == 0xF8 ):              // characters U-00200000 - U-03FFFFFF, mask 111110XX
8763           if( isset( $tmp[$x+4] )) {
8764             $cCnt   += 1;
8765             $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4];
8766             $x      += 4;                             // add a five bytes character
8767           }
8768           break;
8769         case(( $byte & 0xFE ) == 0xFC ):              // characters U-04000000 - U-7FFFFFFF, mask 1111110X
8770           if( isset( $tmp[$x+5] )) {
8771             $cCnt   += 1;
8772             $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4].$tmp[$x+5];
8773             $x      += 5;                             // add a six bytes character
8774           }
8775         default:                                      // add any other byte without counting up $cCnt
8776           break;
8777       } // end switch( TRUE )
8778       $x         += 1;                                // next 'byte' to test
8779     } // end while( TRUE ) {
8780     return $string;
8781   }
8782 /**
8783  * sort callback functions for exdate
8784  *
8785  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8786  * @since 2.16.11 - 2013-01-12
8787  * @param array $a
8788  * @param array $b
8789  * @return int
8790  */
8791   public static function _sortExdate1( $a, $b ) {
8792     $as  = sprintf( '%04d%02d%02d', $a['year'], $a['month'], $a['day'] );
8793     $as .= ( isset( $a['hour'] )) ? sprintf( '%02d%02d%02d', $a['hour'], $a['min'], $a['sec'] ) : '';
8794     $bs  = sprintf( '%04d%02d%02d', $b['year'], $b['month'], $b['day'] );
8795     $bs .= ( isset( $b['hour'] )) ? sprintf( '%02d%02d%02d', $b['hour'], $b['min'], $b['sec'] ) : '';
8796     return strcmp( $as, $bs );
8797   }
8798   public static function _sortExdate2( $a, $b ) {
8799     $val = reset( $a['value'] );
8800     $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8801     $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8802     $val = reset( $b['value'] );
8803     $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8804     $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8805     return strcmp( $as, $bs );
8806   }
8807 /**
8808  * sort callback functions for rdate
8809  *
8810  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8811  * @since 2.16.9 - 2013-01-12
8812  * @param array $a
8813  * @param array $b
8814  * @return int
8815  */
8816   public static function _sortRdate1( $a, $b ) {
8817     $val = isset( $a['year'] ) ? $a : $a[0];
8818     $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8819     $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8820     $val = isset( $b['year'] ) ? $b : $b[0];
8821     $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8822     $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8823     return strcmp( $as, $bs );
8824   }
8825   public static function _sortRdate2( $a, $b ) {
8826     $val = isset( $a['value'][0]['year'] ) ? $a['value'][0] : $a['value'][0][0];
8827     $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8828     $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8829     $val = isset( $b['value'][0]['year'] ) ? $b['value'][0] : $b['value'][0][0];
8830     $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8831     $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8832     return strcmp( $as, $bs );
8833   }
8834 /**
8835  * step date, return updated date, array and timpstamp
8836  *
8837  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8838  * @since 2.14.1 - 2012-09-24
8839  * @param array $date, date to step
8840  * @param int   $timestamp
8841  * @param array $step, default array( 'day' => 1 )
8842  * @return void
8843  */
8844   public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) {
8845     if( !isset( $date['hour'] )) $date['hour'] = 0;
8846     if( !isset( $date['min'] ))  $date['min']  = 0;
8847     if( !isset( $date['sec'] ))  $date['sec']  = 0;
8848     foreach( $step as $stepix => $stepvalue )
8849       $date[$stepix] += $stepvalue;
8850     $timestamp  = mktime( $date['hour'], $date['min'], $date['sec'], $date['month'], $date['day'], $date['year'] );
8851     $d          = date( 'Y-m-d-H-i-s', $timestamp);
8852     $d          = explode( '-', $d );
8853     $date       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
8854     foreach( $date as $k => $v )
8855       $date[$k] = (int) $v;
8856   }
8857 /**
8858  * convert a date from specific string to array format
8859  *
8860  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8861  * @since 2.11.8 - 2012-01-27
8862  * @param mixed $input
8863  * @return bool, TRUE on success
8864  */
8865   public static function _strDate2arr( & $input ) {
8866     if( is_array( $input ))
8867       return FALSE;
8868     if( 5 > strlen( (string) $input ))
8869       return FALSE;
8870     $work = $input;
8871     if( 2 == substr_count( $work, '-' ))
8872       $work = str_replace( '-', '', $work );
8873     if( 2 == substr_count( $work, '/' ))
8874       $work = str_replace( '/', '', $work );
8875     if( !ctype_digit( substr( $work, 0, 8 )))
8876       return FALSE;
8877     $temp = array( 'year'  => (int) substr( $work,  0, 4 )
+8878                  , 'month' => (int) substr( $work,  4, 2 )
+8879                  , 'day'   => (int) substr( $work,  6, 2 ));
8880     if( !checkdate( $temp['month'], $temp['day'], $temp['year'] ))
8881       return FALSE;
8882     if( 8 == strlen( $work )) {
8883       $input = $temp;
8884       return TRUE;
8885     }
8886     if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
8887       $work =  substr( $work, 9 );
8888     elseif( ctype_digit( substr( $work, 8, 1 )))
8889       $work = substr( $work, 8 );
8890     else
8891      return FALSE;
8892     if( 2 == substr_count( $work, ':' ))
8893       $work = str_replace( ':', '', $work );
8894     if( !ctype_digit( substr( $work, 0, 4 )))
8895       return FALSE;
8896     $temp['hour']  = substr( $work, 0, 2 );
8897     $temp['min']   = substr( $work, 2, 2 );
8898     if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
+8899        (( 0 > $temp['min'] )  || ( $temp['min']  > 59 )))
8900       return FALSE;
8901     if( ctype_digit( substr( $work, 4, 2 ))) {
8902       $temp['sec'] = substr( $work, 4, 2 );
8903       if((  0 > $temp['sec'] ) || ( $temp['sec']  > 59 ))
8904         return FALSE;
8905       $len = 6;
8906     }
8907     else {
8908       $temp['sec'] = 0;
8909       $len = 4;
8910     }
8911     if( $len < strlen( $work))
8912       $temp['tz'] = trim( substr( $work, 6 ));
8913     $input = $temp;
8914     return TRUE;
8915   }
8916 /**
8917  * ensures internal date-time/date format for input date-time/date in string fromat
8918  *
8919  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8920  * @since 2.14.1 - 2012-10-07
8921  * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
8922  * @param array $datetime
8923  * @param int   $parno optional, default FALSE
8924  * @param moxed $wtz optional, default null
8925  * @return array
8926  */
8927   public static function _date_time_string( $datetime, $parno=FALSE ) {
8928     return iCalUtilityFunctions::_strdate2date( $datetime, $parno, null );
8929   }
8930   public static function _strdate2date( $datetime, $parno=FALSE, $wtz=null ) {
8931     // save original input string to return it later
8932     $unparseddatetime = $datetime;
8933     $datetime   = (string) trim( $datetime );
8934     $tz         = null;
8935     $offset     = 0;
8936     $tzSts      = FALSE;
8937     $len        = strlen( $datetime );
8938     if( 'Z' == substr( $datetime, -1 )) {
8939       $tz       = 'Z';
8940       $datetime = trim( substr( $datetime, 0, ( $len - 1 )));
8941       $tzSts    = TRUE;
8942       $len      = 88;
8943     }
8944     if( iCalUtilityFunctions::_isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset
8945       $tz       = substr( $datetime, -5, 5 );
8946       $datetime = trim( substr( $datetime, 0, ($len - 5)));
8947       $len      = strlen( $datetime );
8948     }
8949     elseif( iCalUtilityFunctions::_isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset
8950       $tz       = substr( $datetime, -7, 7 );
8951       $datetime = trim( substr( $datetime, 0, ($len - 7)));
8952       $len      = strlen( $datetime );
8953     }
8954     elseif( empty( $wtz ) && ctype_digit( substr( $datetime, 0, 4 )) && ctype_digit( substr( $datetime, -2, 2 )) && iCalUtilityFunctions::_strDate2arr( $datetime )) {
8955       $output = $datetime;
8956       if( !empty( $tz ))
8957         $output['tz'] = 'Z';
8958       $output['unparsedtext'] = $unparseddatetime;
8959       return $output;
8960     }
8961     else {
8962       $cx  = $tx = 0;    //  find any trailing timezone or offset
8963       for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
8964         $char = substr( $datetime, $cx, 1 );
8965         if(( ' ' == $char) || ctype_digit( $char ))
8966           break; // if exists, tz ends here.. . ?
8967         else
8968            $tx--; // tz length counter
8969       }
8970       if( 0 > $tx ) { // if any
8971         $tz     = substr( $datetime, $tx );
8972         $datetime = trim( substr( $datetime, 0, $len + $tx ));
8973         $len    = strlen( $datetime );
8974       }
8975       if(( 17 <= $len ) ||  // long textual datetime
+8976          ( ctype_digit( substr( $datetime, 0, 8 )) && ( 'T' ==  substr( $datetime, 8, 1 )) && ctype_digit( substr( $datetime, -6, 6 ))) ||
+8977          ( ctype_digit( substr( $datetime, 0, 14 )))) {
8978         $len    = 88;
8979         $tzSts  = TRUE;
8980       }
8981       else
8982         $tz     = null; // no tz for Y-m-d dates
8983     }
8984     if( empty( $tz ) && !empty( $wtz ))
8985       $tz       = $wtz;
8986     if( 17 >= $len ) // any Y-m-d textual date
8987       $tz       = null;
8988     if( !empty( $tz ) && ( 17 < $len )) { // tz set AND long textual datetime
8989       if(( 'Z' != $tz ) && ( iCalUtilityFunctions::_isOffset( $tz ))) {
8990         $offset = (string) iCalUtilityFunctions::_tz2offset( $tz ) * -1;
8991         $tz     = 'UTC';
8992         $tzSts  = TRUE;
8993       }
8994       elseif( !empty( $wtz ))
8995         $tzSts  = TRUE;
8996       $tz       = trim( $tz );
8997       if(( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
8998         $tz     = 'UTC';
8999       if( 0 < substr_count( $datetime, '-' ))
9000         $datetime = str_replace( '-', '/', $datetime );
9001       try {
9002         $d        = new DateTime( $datetime, new DateTimeZone( $tz ));
9003         if( 0  != $offset )  // adjust for offset
9004           $d->modify( $offset.' seconds' );
9005         $datestring = $d->format( 'Y-m-d-H-i-s' );
9006         unset( $d );
9007       }
9008       catch( Exception $e ) {
9009         $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
9010       }
9011     } // end if( !empty( $tz ) && ( 17 < $len ))
9012     else
9013       $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
9014 // echo "<tr><td>&nbsp;<td colspan='3'>_strdate2date input=$datetime, tz=$tz, offset=$offset, wtz=$wtz, len=$len, prepDate=$datestring\n";
9015     if( 'UTC' == $tz )
9016       $tz         = 'Z';
9017     $d            = explode( '-', $datestring );
9018     $output       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2] );
9019     if((( FALSE !== $parno ) && ( 3 != $parno )) || // parno is set to 6 or 7
+9020        (( FALSE === $parno ) && ( 'Z' == $tz ))  || // parno is not set and UTC
+9021        (( FALSE === $parno ) && ( 'Z' != $tz ) && ( 0 != $d[3] + $d[4] + $d[5] ) && ( 17 < $len ))) { // !parno and !UTC and 0 != hour+min+sec and long input text
9022       $output['hour'] = $d[3];
9023       $output['min']  = $d[4];
9024       $output['sec']  = $d[5];
9025       if(( $tzSts || ( 7 == $parno )) && !empty( $tz ))
9026         $output['tz'] = $tz;
9027     }
9028     // return original string in the array in case strtotime failed to make sense of it
9029     $output['unparsedtext'] = $unparseddatetime;
9030     return $output;
9031   }
9032 /********************************************************************************/
9033 /**
9034  * special characters management output
9035  *
9036  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9037  * @since 2.16.2 - 2012-12-18
9038  * @param string $string
9039  * @param string $format
9040  * @param string $nl
9041  * @return string
9042  */
9043   public static function _strrep( $string, $format, $nl ) {
9044     switch( $format ) {
9045       case 'xcal':
9046         $string = str_replace( '\n',  $nl, $string);
9047         $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
9048         break;
9049       default:
9050         $pos = 0;
9051         $specChars = array( 'n', 'N', 'r', ',', ';' );
9052         while( isset( $string[$pos] )) {
9053           if( FALSE === ( $pos = strpos( $string, "\\", $pos )))
9054             break;
9055           if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
9056             $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
9057             $pos += 1;
9058           }
9059           $pos += 1;
9060         }
9061         if( FALSE !== strpos( $string, '"' ))
9062           $string = str_replace('"',   "'",       $string);
9063         if( FALSE !== strpos( $string, ',' ))
9064           $string = str_replace(',',   '\,',      $string);
9065         if( FALSE !== strpos( $string, ';' ))
9066           $string = str_replace(';',   '\;',      $string);
9067         if( FALSE !== strpos( $string, "\r\n" ))
9068           $string = str_replace( "\r\n", '\n',    $string);
9069         elseif( FALSE !== strpos( $string, "\r" ))
9070           $string = str_replace( "\r", '\n',      $string);
9071         elseif( FALSE !== strpos( $string, "\n" ))
9072           $string = str_replace( "\n", '\n',      $string);
9073         if( FALSE !== strpos( $string, '\N' ))
9074           $string = str_replace( '\N', '\n',      $string);
9075 //        if( FALSE !== strpos( $string, $nl ))
9076           $string = str_replace( $nl, '\n', $string);
9077         break;
9078     }
9079     return $string;
9080   }
9081 /**
9082  * special characters management input (from iCal file)
9083  *
9084  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9085  * @since 2.16.2 - 2012-12-18
9086  * @param string $string
9087  * @return string
9088  */
9089   public static function _strunrep( $string ) {
9090     $string = str_replace( '\\\\', '\\',     $string);
9091     $string = str_replace( '\,',   ',',      $string);
9092     $string = str_replace( '\;',   ';',      $string);
9093 //    $string = str_replace( '\n',  $nl, $string); // ??
9094     return $string;
9095   }
9096 /**
9097  * convert timestamp to date array, default UTC or adjusted for offset/timezone
9098  *
9099  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9100  * @since 2.15.1 - 2012-10-17
9101  * @param mixed   $timestamp
9102  * @param int     $parno
9103  * @param string  $wtz
9104  * @return array
9105  */
9106   public static function _timestamp2date( $timestamp, $parno=6, $wtz=null ) {
9107     if( is_array( $timestamp )) {
9108       $tz        = ( isset( $timestamp['tz'] )) ? $timestamp['tz'] : $wtz;
9109       $timestamp = $timestamp['timestamp'];
9110     }
9111     $tz          = ( isset( $tz )) ? $tz : $wtz;
9112     if( empty( $tz ) || ( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
9113       $tz        = 'UTC';
9114     elseif( iCalUtilityFunctions::_isOffset( $tz )) {
9115       $offset    = iCalUtilityFunctions::_tz2offset( $tz );
9116       $tz        = 'UTC';
9117     }
9118     try {
9119       $d         = new DateTime( "@$timestamp" );  // set UTC date
9120       if( isset( $offset ) && ( 0 != $offset ))    // adjust for offset
9121         $d->modify( $offset.' seconds' );
9122       elseif( 'UTC' != $tz )
9123         $d->setTimezone( new DateTimeZone( $tz )); // convert to local date
9124       $date      = $d->format( 'Y-m-d-H-i-s' );
9125       unset( $d );
9126     }
9127     catch( Exception $e ) {
9128       $date      = date( 'Y-m-d-H-i-s', $timestamp );
9129     }
9130     $date        = explode( '-', $date );
9131     $output      = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2] );
9132     if( 3 != $parno ) {
9133       $output['hour'] = $date[3];
9134       $output['min']  = $date[4];
9135       $output['sec']  = $date[5];
9136       if( 'UTC' == $tz && ( !isset( $offset ) || ( 0 == $offset )))
9137         $output['tz'] = 'Z';
9138     }
9139     return $output;
9140   }
9141 /**
9142  * convert timestamp (seconds) to duration in array format
9143  *
9144  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9145  * @since 2.6.23 - 2010-10-23
9146  * @param int $timestamp
9147  * @return array, duration format
9148  */
9149   public static function _timestamp2duration( $timestamp ) {
9150     $dur         = array();
9151     $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 ));
9152     $timestamp   =              $timestamp % ( 7 * 24 * 60 * 60 );
9153     $dur['day']  = (int) floor( $timestamp / ( 24 * 60 * 60 ));
9154     $timestamp   =              $timestamp % ( 24 * 60 * 60 );
9155     $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 ));
9156     $timestamp   =              $timestamp % ( 60 * 60 );
9157     $dur['min']  = (int) floor( $timestamp / ( 60 ));
9158     $dur['sec']  = (int)        $timestamp % ( 60 );
9159     return $dur;
9160   }
9161 /**
9162  * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
9163  *
9164  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9165  * @since 2.15.1 - 2012-10-17
9166  * @param mixed  $date,   date to alter
9167  * @param string $tzFrom, PHP valid 'from' timezone
9168  * @param string $tzTo,   PHP valid 'to' timezone, default 'UTC'
9169  * @param string $format, date output format, default 'Ymd\THis'
9170  * @return bool
9171  */
9172   public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
9173     if( is_array( $date ) && isset( $date['timestamp'] )) {
9174       try {
9175         $d = new DateTime( "@{$date['timestamp']}" ); // set UTC date
9176         $d->setTimezone(new DateTimeZone( $tzFrom )); // convert to 'from' date
9177       }
9178       catch( Exception $e ) { return FALSE; }
9179     }
9180     else {
9181       if( iCalUtilityFunctions::_isArrayDate( $date )) {
9182         if( isset( $date['tz'] ))
9183           unset( $date['tz'] );
9184         $date  = iCalUtilityFunctions::_date2strdate( iCalUtilityFunctions::_chkDateArr( $date ));
9185       }
9186       if( 'Z' == substr( $date, -1 ))
9187         $date = substr( $date, 0, ( strlen( $date ) - 2 ));
9188       try { $d = new DateTime( $date, new DateTimeZone( $tzFrom )); }
9189       catch( Exception $e ) { return FALSE; }
9190     }
9191     try { $d->setTimezone( new DateTimeZone( $tzTo )); }
9192     catch( Exception $e ) { return FALSE; }
9193     $date = $d->format( $format );
9194     return TRUE;
9195   }
9196 /**
9197  * convert offset, [+/-]HHmm[ss], to seconds, used when correcting UTC to localtime or v.v.
9198  *
9199  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9200  * @since 2.11.4 - 2012-01-11
9201  * @param string $offset
9202  * @return integer
9203  */
9204   public static function _tz2offset( $tz ) {
9205     $tz           = trim( (string) $tz );
9206     $offset       = 0;
9207     if(((     5  != strlen( $tz ))       && ( 7  != strlen( $tz ))) ||
+9208        ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
+9209        (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
+9210            (( 7  == strlen( $tz ))       && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
9211       return $offset;
9212     $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
9213     $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
9214     $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
9215     $offset       = $hours2sec + $min2sec + $sec;
9216     $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
9217     return $offset;
9218   }
9219 }
9220 /*********************************************************************************/
9221 /*          iCalcreator vCard helper functions                                   */
9222 /*********************************************************************************/
9223 /**
9224  * convert single ATTENDEE, CONTACT or ORGANIZER (in email format) to vCard
9225  * returns vCard/TRUE or if directory (if set) or file write is unvalid, FALSE
9226  *
9227  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9228  * @since 2.12.2 - 2012-07-11
9229  * @param object $email
9230  * $param string $version, vCard version (default 2.1)
9231  * $param string $directory, where to save vCards (default FALSE)
9232  * $param string $ext, vCard file extension (default 'vcf')
9233  * @return mixed
9234  */
9235 function iCal2vCard( $email, $version='2.1', $directory=FALSE, $ext='vcf' ) {
9236   if( FALSE === ( $pos = strpos( $email, '@' )))
9237     return FALSE;
9238   if( $directory ) {
9239     if( DIRECTORY_SEPARATOR != substr( $directory, ( 0 - strlen( DIRECTORY_SEPARATOR ))))
9240       $directory .= DIRECTORY_SEPARATOR;
9241     if( !is_dir( $directory ) || !is_writable( $directory ))
9242       return FALSE;
9243   }
9244             /* prepare vCard */
9245   $email  = str_replace( 'MAILTO:', '', $email );
9246   $name   = $person = substr( $email, 0, $pos );
9247   if( ctype_upper( $name ) || ctype_lower( $name ))
9248     $name = array( $name );
9249   else {
9250     if( FALSE !== ( $pos = strpos( $name, '.' ))) {
9251       $name = explode( '.', $name );
9252       foreach( $name as $k => $part )
9253         $name[$k] = ucfirst( $part );
9254     }
9255     else { // split camelCase
9256       $chars = $name;
9257       $name  = array( $chars[0] );
9258       $k     = 0;
9259       $x     = 1;
9260       while( FALSE !== ( $char = substr( $chars, $x, 1 ))) {
9261         if( ctype_upper( $char )) {
9262           $k += 1;
9263           $name[$k] = '';
9264         }
9265         $name[$k]  .= $char;
9266         $x++;
9267       }
9268     }
9269   }
9270   $nl     = "\r\n";
9271   $FN     = 'FN:'.implode( ' ', $name ).$nl;
9272   $name   = array_reverse( $name );
9273   $N      = 'N:'.array_shift( $name );
9274   $scCnt  = 0;
9275   while( NULL != ( $part = array_shift( $name ))) {
9276     if(( '4.0' != $version ) || ( 4 > $scCnt ))
9277       $scCnt += 1;
9278     $N   .= ';'.$part;
9279   }
9280   while(( '4.0' == $version ) && ( 4 > $scCnt )) {
9281     $N   .= ';';
9282     $scCnt += 1;
9283   }
9284   $N     .= $nl;
9285   $EMAIL  = 'EMAIL:'.$email.$nl;
9286            /* create vCard */
9287   $vCard  = 'BEGIN:VCARD'.$nl;
9288   $vCard .= "VERSION:$version$nl";
9289   $vCard .= 'PRODID:-//kigkonsult.se '.ICALCREATOR_VERSION."//$nl";
9290   $vCard .= $N;
9291   $vCard .= $FN;
9292   $vCard .= $EMAIL;
9293   $vCard .= 'REV:'.gmdate( 'Ymd\THis\Z' ).$nl;
9294   $vCard .= 'END:VCARD'.$nl;
9295             /* save each vCard as (unique) single file */
9296   if( $directory ) {
9297     $fname = $directory.preg_replace( '/[^a-z0-9.]/i', '', $email );
9298     $cnt   = 1;
9299     $dbl   = '';
9300     while( is_file ( $fname.$dbl.'.'.$ext )) {
9301       $cnt += 1;
9302       $dbl = "_$cnt";
9303     }
9304     if( FALSE === file_put_contents( $fname, $fname.$dbl.'.'.$ext ))
9305       return FALSE;
9306     return TRUE;
9307   }
9308             /* return vCard */
9309   else
9310     return $vCard;
9311 }
9312 /**
9313  * convert ATTENDEEs, CONTACTs and ORGANIZERs (in email format) to vCards
9314  *
9315  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9316  * @since 2.12.2 - 2012-05-07
9317  * @param object $calendar, iCalcreator vcalendar instance reference
9318  * $param string $version, vCard version (default 2.1)
9319  * $param string $directory, where to save vCards (default FALSE)
9320  * $param string $ext, vCard file extension (default 'vcf')
9321  * @return mixed
9322  */
9323 function iCal2vCards( & $calendar, $version='2.1', $directory=FALSE, $ext='vcf' ) {
9324   $hits   = array();
9325   $vCardP = array( 'ATTENDEE', 'CONTACT', 'ORGANIZER' );
9326   foreach( $vCardP as $prop ) {
9327     $hits2 = $calendar->getProperty( $prop );
9328     foreach( $hits2 as $propValue => $occCnt ) {
9329       if( FALSE === ( $pos = strpos( $propValue, '@' )))
9330         continue;
9331       $propValue = str_replace( 'MAILTO:', '', $propValue );
9332       if( isset( $hits[$propValue] ))
9333         $hits[$propValue] += $occCnt;
9334       else
9335         $hits[$propValue]  = $occCnt;
9336     }
9337   }
9338   if( empty( $hits ))
9339     return FALSE;
9340   ksort( $hits );
9341   $output   = '';
9342   foreach( $hits as $email => $skip ) {
9343     $res = iCal2vCard( $email, $version, $directory, $ext );
9344     if( $directory && !$res )
9345       return FALSE;
9346     elseif( !$res )
9347       return $res;
9348     else
9349       $output .= $res;
9350   }
9351   if( $directory )
9352     return TRUE;
9353   if( !empty( $output ))
9354     return $output;
9355   return FALSE;
9356 }
9357 /*********************************************************************************/
9358 /*          iCalcreator XML (rfc6321) helper functions                           */
9359 /*********************************************************************************/
9360 /**
9361  * format iCal XML output, rfc6321, using PHP SimpleXMLElement
9362  *
9363  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9364  * @since 2.15.6 - 2012-10-19
9365  * @param object $calendar, iCalcreator vcalendar instance reference
9366  * @return string
9367  */
9368 function iCal2XML( & $calendar ) {
9369             /** fix an SimpleXMLElement instance and create root element */
9370   $xmlstr     = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
9371   $xmlstr    .= '<!-- created utilizing kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
9372   $xmlstr    .= '</icalendar>';
9373   $xml        = new SimpleXMLElement( $xmlstr );
9374   $vcalendar  = $xml->addChild( 'vcalendar' );
9375             /** fix calendar properties */
9376   $properties = $vcalendar->addChild( 'properties' );
9377   $calProps = array( 'prodid', 'version', 'calscale', 'method' );
9378   foreach( $calProps as $calProp ) {
9379     if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
9380       _addXMLchild( $properties, $calProp, 'text', $content );
9381   }
9382   while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
9383     _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9384   $langCal = $calendar->getConfig( 'language' );
9385             /** prepare to fix components with properties */
9386   $components    = $vcalendar->addChild( 'components' );
9387   $comps         = array( 'vtimezone', 'vevent', 'vtodo', 'vjournal', 'vfreebusy' );
9388   foreach( $comps as $compName ) {
9389     switch( $compName ) {
9390       case 'vevent':
9391       case 'vtodo':
9392         $subComps     = array( 'valarm' );
9393         break;
9394       case 'vjournal':
9395       case 'vfreebusy':
9396         $subComps     = array();
9397         break;
9398       case 'vtimezone':
9399         $subComps     = array( 'standard', 'daylight' );
9400         break;
9401     } // end switch( $compName )
9402             /** fix component properties */
9403     while( FALSE !== ( $component = $calendar->getComponent( $compName ))) {
9404       $child      = $components->addChild( $compName );
9405       $properties = $child->addChild( 'properties' );
9406       $langComp   = $component->getConfig( 'language' );
9407       $props      = $component->getConfig( 'setPropertyNames' );
9408       foreach( $props as $prop ) {
9409         switch( strtolower( $prop )) {
9410           case 'attach':          // may occur multiple times, below
9411             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9412               $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
9413               unset( $content['params']['VALUE'] );
9414               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9415             }
9416             break;
9417           case 'attendee':
9418             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9419               if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9420                 if( $langComp )
9421                   $content['params']['LANGUAGE'] = $langComp;
9422                 elseif( $langCal )
9423                   $content['params']['LANGUAGE'] = $langCal;
9424               }
9425               _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9426             }
9427             break;
9428           case 'exdate':
9429             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9430               $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
9431               unset( $content['params']['VALUE'] );
9432               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9433             }
9434             break;
9435           case 'freebusy':
9436             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9437               if( is_array( $content ) && isset( $content['value']['fbtype'] )) {
9438                 $content['params']['FBTYPE'] = $content['value']['fbtype'];
9439                 unset( $content['value']['fbtype'] );
9440               }
9441               _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
9442             }
9443             break;
9444           case 'request-status':
9445             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9446               if( !isset( $content['params']['LANGUAGE'] )) {
9447                 if( $langComp )
9448                   $content['params']['LANGUAGE'] = $langComp;
9449                 elseif( $langCal )
9450                   $content['params']['LANGUAGE'] = $langCal;
9451               }
9452               _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
9453             }
9454             break;
9455           case 'rdate':
9456             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9457               $type = 'date-time';
9458               if( isset( $content['params']['VALUE'] )) {
9459                 if( 'DATE' == $content['params']['VALUE'] )
9460                   $type = 'date';
9461                 elseif( 'PERIOD' == $content['params']['VALUE'] )
9462                   $type = 'period';
9463               }
9464               unset( $content['params']['VALUE'] );
9465               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9466             }
9467             break;
9468           case 'categories':
9469           case 'comment':
9470           case 'contact':
9471           case 'description':
9472           case 'related-to':
9473           case 'resources':
9474             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9475               if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
9476                 if( $langComp )
9477                   $content['params']['LANGUAGE'] = $langComp;
9478                 elseif( $langCal )
9479                   $content['params']['LANGUAGE'] = $langCal;
9480               }
9481               _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9482             }
9483             break;
9484           case 'x-prop':
9485             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9486               _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9487             break;
9488           case 'created':         // single occurence below, if set
9489           case 'completed':
9490           case 'dtstamp':
9491           case 'last-modified':
9492             $utcDate = TRUE;
9493           case 'dtstart':
9494           case 'dtend':
9495           case 'due':
9496           case 'recurrence-id':
9497             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9498               $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
9499               unset( $content['params']['VALUE'] );
9500               if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
9501                 unset( $content['params']['TZID'] );
9502               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9503             }
9504             unset( $utcDate );
9505             break;
9506           case 'duration':
9507             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9508               if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
9509                 $content['params']['RELATED'] = 'END';
9510               _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
9511             }
9512             break;
9513           case 'rrule':
9514             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9515               _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
9516             break;
9517           case 'class':
9518           case 'location':
9519           case 'status':
9520           case 'summary':
9521           case 'transp':
9522           case 'tzid':
9523           case 'uid':
9524             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9525               if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
9526                 if( $langComp )
9527                   $content['params']['LANGUAGE'] = $langComp;
9528                 elseif( $langCal )
9529                   $content['params']['LANGUAGE'] = $langCal;
9530               }
9531               _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9532             }
9533             break;
9534           case 'geo':
9535             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9536               _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
9537             break;
9538           case 'organizer':
9539             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9540               if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9541                 if( $langComp )
9542                   $content['params']['LANGUAGE'] = $langComp;
9543                 elseif( $langCal )
9544                   $content['params']['LANGUAGE'] = $langCal;
9545               }
9546               _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9547             }
9548             break;
9549           case 'percent-complete':
9550           case 'priority':
9551           case 'sequence':
9552             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9553               _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
9554             break;
9555           case 'tzurl':
9556           case 'url':
9557             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9558               _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
9559             break;
9560         } // end switch( $prop )
9561       } // end foreach( $props as $prop )
9562             /** fix subComponent properties, if any */
9563       foreach( $subComps as $subCompName ) {
9564         while( FALSE !== ( $subcomp = $component->getComponent( $subCompName ))) {
9565           $child2       = $child->addChild( $subCompName );
9566           $properties   = $child2->addChild( 'properties' );
9567           $langComp     = $subcomp->getConfig( 'language' );
9568           $subCompProps = $subcomp->getConfig( 'setPropertyNames' );
9569           foreach( $subCompProps as $prop ) {
9570             switch( strtolower( $prop )) {
9571               case 'attach':          // may occur multiple times, below
9572                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9573                   $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
9574                   unset( $content['params']['VALUE'] );
9575                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9576                 }
9577                 break;
9578               case 'attendee':
9579                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9580                   if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9581                     if( $langComp )
9582                       $content['params']['LANGUAGE'] = $langComp;
9583                     elseif( $langCal )
9584                       $content['params']['LANGUAGE'] = $langCal;
9585                   }
9586                   _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9587                 }
9588                 break;
9589               case 'comment':
9590               case 'tzname':
9591                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9592                   if( !isset( $content['params']['LANGUAGE'] )) {
9593                     if( $langComp )
9594                       $content['params']['LANGUAGE'] = $langComp;
9595                     elseif( $langCal )
9596                       $content['params']['LANGUAGE'] = $langCal;
9597                   }
9598                   _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9599                 }
9600                 break;
9601               case 'rdate':
9602                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9603                   $type = 'date-time';
9604                   if( isset( $content['params']['VALUE'] )) {
9605                     if( 'DATE' == $content['params']['VALUE'] )
9606                       $type = 'date';
9607                     elseif( 'PERIOD' == $content['params']['VALUE'] )
9608                       $type = 'period';
9609                   }
9610                   unset( $content['params']['VALUE'] );
9611                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9612                 }
9613                 break;
9614               case 'x-prop':
9615                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9616                   _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9617                 break;
9618               case 'action':      // single occurence below, if set
9619               case 'description':
9620               case 'summary':
9621                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9622                   if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
9623                     if( $langComp )
9624                       $content['params']['LANGUAGE'] = $langComp;
9625                     elseif( $langCal )
9626                       $content['params']['LANGUAGE'] = $langCal;
9627                   }
9628                   _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9629                 }
9630                 break;
9631               case 'dtstart':
9632                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9633                   unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
9634                   _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
9635                 }
9636                 break;
9637               case 'duration':
9638                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9639                   _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
9640                 break;
9641               case 'repeat':
9642                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9643                   _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
9644                 break;
9645               case 'trigger':
9646                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9647                   if( isset( $content['value']['year'] )   &&
9648                       isset( $content['value']['month'] )  &&
9649                       isset( $content['value']['day'] ))
9650                     $type = 'date-time';
9651                   else {
9652                     $type = 'duration';
9653                     if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
9654                       $content['params']['RELATED'] = 'END';
9655                   }
9656                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9657                 }
9658                 break;
9659               case 'tzoffsetto':
9660               case 'tzoffsetfrom':
9661                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9662                   _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
9663                 break;
9664               case 'rrule':
9665                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9666                   _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
9667                 break;
9668             } // switch( $prop )
9669           } // end foreach( $subCompProps as $prop )
9670         } // end while( FALSE !== ( $subcomp = $component->getComponent( subCompName )))
9671       } // end foreach( $subCombs as $subCompName )
9672     } // end while( FALSE !== ( $component = $calendar->getComponent( $compName )))
9673   } // end foreach( $comps as $compName)
9674   return $xml->asXML();
9675 }
9676 /**
9677  * Add children to a SimpleXMLelement
9678  *
9679  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9680  * @since 2.15.5 - 2012-10-19
9681  * @param object $parent,  reference to a SimpleXMLelement node
9682  * @param string $name,    new element node name
9683  * @param string $type,    content type, subelement(-s) name
9684  * @param string $content, new subelement content
9685  * @param array  $params,  new element 'attributes'
9686  * @return void
9687  */
9688 function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
9689             /** create new child node */
9690   $name  = strtolower( $name );
9691   $child = $parent->addChild( $name );
9692   if( isset( $params['VALUE'] ))
9693     unset( $params['VALUE'] );
9694   if( !empty( $params )) {
9695     $parameters = $child->addChild( 'parameters' );
9696     foreach( $params as $param => $parVal ) {
9697       $param = strtolower( $param );
9698       if( 'x-' == substr( $param, 0, 2  )) {
9699         $p1 = $parameters->addChild( $param );
9700         $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
9701       }
9702       else {
9703         $p1 = $parameters->addChild( $param );
9704         switch( $param ) {
9705           case 'altrep':
9706           case 'dir':            $ptype = 'uri';            break;
9707           case 'delegated-from':
9708           case 'delegated-to':
9709           case 'member':
9710           case 'sent-by':        $ptype = 'cal-address';    break;
9711           case 'rsvp':           $ptype = 'boolean';        break ;
9712           default:               $ptype = 'text';           break;
9713         }
9714         if( is_array( $parVal )) {
9715           foreach( $parVal as $pV )
9716             $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
9717         }
9718         else
9719           $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
9720       }
9721     }
9722   }
9723   if( empty( $content ) && ( '0' != $content ))
9724     return;
9725             /** store content */
9726   switch( $type ) {
9727     case 'binary':
9728       $v = $child->addChild( $type, $content );
9729       break;
9730     case 'boolean':
9731       break;
9732     case 'cal-address':
9733       $v = $child->addChild( $type, $content );
9734       break;
9735     case 'date':
9736       if( array_key_exists( 'year', $content ))
9737         $content = array( $content );
9738       foreach( $content as $date ) {
9739         $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] );
9740         $v = $child->addChild( $type, $str );
9741       }
9742       break;
9743     case 'date-time':
9744       if( array_key_exists( 'year', $content ))
9745         $content = array( $content );
9746       foreach( $content as $dt ) {
9747         if( !isset( $dt['hour'] )) $dt['hour'] = 0;
9748         if( !isset( $dt['min'] ))  $dt['min']  = 0;
9749         if( !isset( $dt['sec'] ))  $dt['sec']  = 0;
9750         $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
9751         if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
9752           $str .= 'Z';
9753         $v = $child->addChild( $type, $str );
9754       }
9755       break;
9756     case 'duration':
9757       $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
9758       $v = $child->addChild( $type, $output.iCalUtilityFunctions::_duration2str( $content ) );
9759       break;
9760     case 'geo':
9761       $v1 = $child->addChild( 'latitude',  number_format( (float) $content['latitude'],  6, '.', '' ));
9762       $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' ));
9763       break;
9764     case 'integer':
9765       $v = $child->addChild( $type, $content );
9766       break;
9767     case 'period':
9768       if( !is_array( $content ))
9769         break;
9770       foreach( $content as $period ) {
9771         $v1 = $child->addChild( $type );
9772         $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] );
9773         if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
9774           $str .= 'Z';
9775         $v2 = $v1->addChild( 'start', $str );
9776         if( array_key_exists( 'year', $period[1] )) {
9777           $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] );
9778           if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
9779             $str .= 'Z';
9780           $v2 = $v1->addChild( 'end', $str );
9781         }
9782         else
9783           $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_duration2str( $period[1] ));
9784       }
9785       break;
9786     case 'recur':
9787       foreach( $content as $rulelabel => $rulevalue ) {
9788         $rulelabel = strtolower( $rulelabel );
9789         switch( $rulelabel ) {
9790           case 'until':
9791             if( isset( $rulevalue['hour'] ))
9792               $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] );
9793             else
9794               $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] );
9795             $v = $child->addChild( $rulelabel, $str );
9796             break;
9797           case 'bysecond':
9798           case 'byminute':
9799           case 'byhour':
9800           case 'bymonthday':
9801           case 'byyearday':
9802           case 'byweekno':
9803           case 'bymonth':
9804           case 'bysetpos': {
9805             if( is_array( $rulevalue )) {
9806               foreach( $rulevalue as $vix => $valuePart )
9807                 $v = $child->addChild( $rulelabel, $valuePart );
9808             }
9809             else
9810               $v = $child->addChild( $rulelabel, $rulevalue );
9811             break;
9812           }
9813           case 'byday': {
9814             if( isset( $rulevalue['DAY'] )) {
9815               $str  = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
9816               $str .= $rulevalue['DAY'];
9817               $p    = $child->addChild( $rulelabel, $str );
9818             }
9819             else {
9820               foreach( $rulevalue as $valuePart ) {
9821                 if( isset( $valuePart['DAY'] )) {
9822                   $str  = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
9823                   $str .= $valuePart['DAY'];
9824                   $p    = $child->addChild( $rulelabel, $str );
9825                 }
9826                 else
9827                   $p    = $child->addChild( $rulelabel, $valuePart );
9828               }
9829             }
9830             break;
9831           }
9832           case 'freq':
9833           case 'count':
9834           case 'interval':
9835           case 'wkst':
9836           default:
9837             $p = $child->addChild( $rulelabel, $rulevalue );
9838             break;
9839         } // end switch( $rulelabel )
9840       } // end foreach( $content as $rulelabel => $rulevalue )
9841       break;
9842     case 'rstatus':
9843       $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
9844       $v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
9845       if( isset( $content['extdata'] ))
9846         $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
9847       break;
9848     case 'text':
9849       if( !is_array( $content ))
9850         $content = array( $content );
9851       foreach( $content as $part )
9852         $v = $child->addChild( $type, htmlspecialchars( $part ));
9853       break;
9854     case 'time':
9855       break;
9856     case 'uri':
9857       $v = $child->addChild( $type, $content );
9858       break;
9859     case 'utc-offset':
9860       if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
9861         $str     = substr( $content, 0, 1 );
9862         $content = substr( $content, 1 );
9863       }
9864       else
9865         $str     = '+';
9866       $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
9867       if( 4 < strlen( $content ))
9868         $str .= ':'.substr( $content, 4 );
9869       $v = $child->addChild( $type, $str );
9870       break;
9871     case 'unknown':
9872     default:
9873       if( is_array( $content ))
9874         $content = implode( '', $content );
9875       $v = $child->addChild( 'unknown', htmlspecialchars( $content ));
9876       break;
9877   }
9878 }
9879 /**
9880  * parse xml string into iCalcreator instance
9881  *
9882  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9883  * @since 2.11.2 - 2012-01-31
9884  * @param  string $xmlstr
9885  * @param  array  $iCalcfg iCalcreator config array (opt)
9886  * @return mixed  iCalcreator instance or FALSE on error
9887  */
9888 function & XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
9889   libxml_use_internal_errors( TRUE );
9890   $xml = simplexml_load_string( $xmlstr );
9891   if( !$xml ) {
9892     $str    = '';
9893     $return = FALSE;
9894     foreach( libxml_get_errors() as $error ) {
9895       switch ( $error->level ) {
9896         case LIBXML_ERR_FATAL:   $str .= ' FATAL ';   break;
9897         case LIBXML_ERR_ERROR:   $str .= ' ERROR ';   break;
9898         case LIBXML_ERR_WARNING:
9899         default:                 $str .= ' WARNING '; break;
9900       }
9901       $str .= PHP_EOL.'Error when loading XML';
9902       if( !empty( $error->file ))
9903         $str .= ',  file:'.$error->file.', ';
9904       $str .= ', line:'.$error->line;
9905       $str .= ', ('.$error->code.') '.$error->message;
9906     }
9907     error_log( $str );
9908     if( LIBXML_ERR_WARNING != $error->level )
9909       return $return;
9910     libxml_clear_errors();
9911   }
9912   return xml2iCal( $xml, $iCalcfg );
9913 }
9914 /**
9915  * parse xml file into iCalcreator instance
9916  *
9917  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9918  * @since  2.11.2 - 2012-01-20
9919  * @param  string $xmlfile
9920  * @param  array$iCalcfg iCalcreator config array (opt)
9921  * @return mixediCalcreator instance or FALSE on error
9922  */
9923 function & XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
9924   libxml_use_internal_errors( TRUE );
9925   $xml = simplexml_load_file( $xmlfile );
9926   if( !$xml ) {
9927     $str = '';
9928     foreach( libxml_get_errors() as $error ) {
9929       switch ( $error->level ) {
9930         case LIBXML_ERR_FATAL:   $str .= 'FATAL ';   break;
9931         case LIBXML_ERR_ERROR:   $str .= 'ERROR ';   break;
9932         case LIBXML_ERR_WARNING:
9933         default:                 $str .= 'WARNING '; break;
9934       }
9935       $str .= 'Failed loading XML'.PHP_EOL;
9936       if( !empty( $error->file ))
9937         $str .= ' file:'.$error->file.', ';
9938       $str .= 'line:'.$error->line.PHP_EOL;
9939       $str .= '('.$error->code.') '.$error->message.PHP_EOL;
9940     }
9941     error_log( $str );
9942     if( LIBXML_ERR_WARNING != $error->level )
9943       return FALSE;
9944     libxml_clear_errors();
9945   }
9946   return xml2iCal( $xml, $iCalcfg );
9947 }
9948 /**
9949  * parse SimpleXMLElement instance into iCalcreator instance
9950  *
9951  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9952  * @since  2.11.2 - 2012-01-27
9953  * @param  object $xmlobj  SimpleXMLElement
9954  * @param  array  $iCalcfg iCalcreator config array (opt)
9955  * @return mixed  iCalcreator instance or FALSE on error
9956  */
9957 function & XML2iCal( $xmlobj, $iCalcfg=array()) {
9958   $iCal = new vcalendar( $iCalcfg );
9959   foreach( $xmlobj->children() as $icalendar ) { // vcalendar
9960     foreach( $icalendar->children() as $calPart ) { // calendar properties and components
9961       if( 'components' == $calPart->getName()) {
9962         foreach( $calPart->children() as $component ) { // single components
9963           if( 0 < $component->count())
9964             _getXMLComponents( $iCal, $component );
9965         }
9966       }
9967       elseif(( 'properties' == $calPart->getName()) && ( 0 < $calPart->count())) {
9968         foreach( $calPart->children() as $calProp ) { // calendar properties
9969          $propName = $calProp->getName();
9970           if(( 'calscale' != $propName ) && ( 'method' != $propName ) && ( 'x-' != substr( $propName,0,2 )))
9971             continue;
9972           $params = array();
9973           foreach( $calProp->children() as $calPropElem ) { // single calendar property
9974             if( 'parameters' == $calPropElem->getName())
9975               $params = _getXMLParams( $calPropElem );
9976             else
9977               $iCal->setProperty( $propName, reset( $calPropElem ), $params );
9978           } // end foreach( $calProp->children() as $calPropElem )
9979         } // end foreach( $calPart->properties->children() as $calProp )
9980       } // end if( 0 < $calPart->properties->count())
9981     } // end foreach( $icalendar->children() as $calPart )
9982   } // end foreach( $xmlobj->children() as $icalendar )
9983   return $iCal;
9984 }
9985 /**
9986  * parse SimpleXMLElement instance property parameters and return iCalcreator property parameter array
9987  *
9988  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9989  * @since  2.11.2 - 2012-01-15
9990  * @param  object $parameters SimpleXMLElement
9991  * @return array  iCalcreator property parameter array
9992  */
9993 function _getXMLParams( & $parameters ) {
9994   if( 1 > $parameters->count())
9995     return array();
9996   $params = array();
9997   foreach( $parameters->children() as $parameter ) { // single parameter key
9998     $key   = strtoupper( $parameter->getName());
9999     $value = array();
10000     foreach( $parameter->children() as $paramValue ) // skip parameter value type
10001       $value[] = reset( $paramValue );
10002     if( 2 > count( $value ))
10003       $params[$key] = html_entity_decode( reset( $value ));
10004     else
10005       $params[$key] = $value;
10006   }
10007   return $params;
10008 }
10009 /**
10010  * parse SimpleXMLElement instance components, create iCalcreator component and update
10011  *
10012  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10013  * @since  2.11.2 - 2012-01-15
10014  * @param  array  $iCal iCalcreator calendar instance
10015  * @param  object $component SimpleXMLElement
10016  * @return void
10017  */
10018 function _getXMLComponents( & $iCal, & $component ) {
10019   $compName = $component->getName();
10020   $comp     = & $iCal->newComponent( $compName );
10021   $subComponents = array( 'valarm', 'standard', 'daylight' );
10022   foreach( $component->children() as $compPart ) { // properties and (opt) subComponents
10023     if( 1 > $compPart->count())
10024       continue;
10025     if( in_array( $compPart->getName(), $subComponents ))
10026       _getXMLComponents( $comp, $compPart );
10027     elseif( 'properties' == $compPart->getName()) {
10028       foreach( $compPart->children() as $property ) // properties as single property
10029         _getXMLProperties( $comp, $property );
10030     }
10031   } // end foreach( $component->children() as $compPart )
10032 }
10033 /**
10034  * parse SimpleXMLElement instance property, create iCalcreator component property
10035  *
10036  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10037  * @since  2.11.2 - 2012-01-27
10038  * @param  array  $iCal iCalcreator calendar instance
10039  * @param  object $component SimpleXMLElement
10040  * @return void
10041  */
10042 function _getXMLProperties( & $iCal, & $property ) {
10043   $propName  = $property->getName();
10044   $value     = $params = array();
10045   $valueType = '';
10046   foreach( $property->children() as $propPart ) { // calendar property parameters (opt) and value(-s)
10047     $valueType = $propPart->getName();
10048     if( 'parameters' == $valueType) {
10049       $params = _getXMLParams( $propPart );
10050       continue;
10051     }
10052     switch( $valueType ) {
10053       case 'binary':
10054         $value = reset( $propPart );
10055         break;
10056       case 'boolean':
10057         break;
10058       case 'cal-address':
10059         $value = reset( $propPart );
10060         break;
10061       case 'date':
10062         $params['VALUE'] = 'DATE';
10063       case 'date-time':
10064         if(( 'exdate' == $propName ) || ( 'rdate' == $propName ))
10065           $value[] = reset( $propPart );
10066         else
10067           $value = reset( $propPart );
10068         break;
10069       case 'duration':
10070         $value = reset( $propPart );
10071         break;
10072 //        case 'geo':
10073       case 'latitude':
10074       case 'longitude':
10075         $value[$valueType] = reset( $propPart );
10076         break;
10077       case 'integer':
10078         $value = reset( $propPart );
10079         break;
10080       case 'period':
10081         if( 'rdate' == $propName )
10082           $params['VALUE'] = 'PERIOD';
10083         $pData = array();
10084         foreach( $propPart->children() as $periodPart )
10085           $pData[] = reset( $periodPart );
10086         if( !empty( $pData ))
10087           $value[] = $pData;
10088         break;
10089 //        case 'rrule':
10090       case 'freq':
10091       case 'count':
10092       case 'until':
10093       case 'interval':
10094       case 'wkst':
10095         $value[$valueType] = reset( $propPart );
10096         break;
10097       case 'bysecond':
10098       case 'byminute':
10099       case 'byhour':
10100       case 'bymonthday':
10101       case 'byyearday':
10102       case 'byweekno':
10103       case 'bymonth':
10104       case 'bysetpos':
10105         $value[$valueType][] = reset( $propPart );
10106         break;
10107       case 'byday':
10108         $byday = reset( $propPart );
10109         if( 2 == strlen( $byday ))
10110           $value[$valueType][] = array( 'DAY' => $byday );
10111         else {
10112           $day = substr( $byday, -2 );
10113           $key = substr( $byday, 0, ( strlen( $byday ) - 2 ));
10114           $value[$valueType][] = array( $key, 'DAY' => $day );
10115         }
10116         break;
10117 //      case 'rstatus':
10118       case 'code':
10119         $value[0] = reset( $propPart );
10120         break;
10121       case 'description':
10122         $value[1] = reset( $propPart );
10123         break;
10124       case 'data':
10125         $value[2] = reset( $propPart );
10126         break;
10127       case 'text':
10128         $text = str_replace( array( "\r\n", "\n\r", "\r", "\n"), '\n', reset( $propPart ));
10129         $value['text'][] = html_entity_decode( $text );
10130         break;
10131       case 'time':
10132         break;
10133       case 'uri':
10134         $value = reset( $propPart );
10135         break;
10136       case 'utc-offset':
10137         $value = str_replace( ':', '', reset( $propPart ));
10138         break;
10139       case 'unknown':
10140       default:
10141         $value = html_entity_decode( reset( $propPart ));
10142         break;
10143     } // end switch( $valueType )
10144   } // end  foreach( $property->children() as $propPart )
10145   if( 'freebusy' == $propName ) {
10146     $fbtype = $params['FBTYPE'];
10147     unset( $params['FBTYPE'] );
10148     $iCal->setProperty( $propName, $fbtype, $value, $params );
10149   }
10150   elseif( 'geo' == $propName )
10151     $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
10152   elseif( 'request-status' == $propName ) {
10153     if( !isset( $value[2] ))
10154       $value[2] = FALSE;
10155     $iCal->setProperty( $propName, $value[0], $value[1], $value[2], $params );
10156   }
10157   else {
10158     if( isset( $value['text'] ) && is_array( $value['text'] )) {
10159       if(( 'categories' == $propName ) || ( 'resources' == $propName ))
10160         $value = $value['text'];
10161       else
10162         $value = reset( $value['text'] );
10163     }
10164     $iCal->setProperty( $propName, $value, $params );
10165   }
10166 }
10167 /*********************************************************************************/
10168 /*          Additional functions to use with vtimezone components                */
10169 /*********************************************************************************/
10170 /**
10171  * For use with
10172  * iCalcreator (kigkonsult.se/iCalcreator/index.php)
10173  * copyright (c) 2011 Yitzchok Lavi
10174  * icalcreator@onebigsystem.com
10175  *
10176  * This library is free software; you can redistribute it and/or
10177  * modify it under the terms of the GNU Lesser General Public
10178  * License as published by the Free Software Foundation; either
10179  * version 2.1 of the License, or (at your option) any later version.
10180  *
10181  * This library is distributed in the hope that it will be useful,
10182  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10183  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10184  * Lesser General Public License for more details.
10185  *
10186  * You should have received a copy of the GNU Lesser General Public
10187  * License along with this library; if not, write to the Free Software
10188  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
10189  */
10190 /**
10191  * Additional functions to use with vtimezone components
10192  *
10193  * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')!
10194  *
10195  * @author Yitzchok Lavi <icalcreator@onebigsystem.com>
10196  *         adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10197  * @version 1.0.2 - 2011-02-24
10198  *
10199  */
10200 /**
10201  * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the
10202  * timezone, according to the VTIMEZONE information in the input array.
10203  *
10204  * $param array  $timezonesarray, output from function getTimezonesAsDateArrays (below)
10205  * $param string $tzid,           time zone identifier
10206  * $param mixed  $timestamp,      timestamp or a UTC datetime (in array format)
10207  * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname'
10208  *
10209  */
10210 function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) {
10211     if( is_array( $timestamp )) {
10212 //$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] ); // test ###
10213       $timestamp = gmmktime(
+10214             $timestamp['hour'],
+10215             $timestamp['min'],
+10216             $timestamp['sec'],
+10217             $timestamp['month'],
+10218             $timestamp['day'],
+10219             $timestamp['year']
+10220             ) ;
10221 // echo '<td colspan="4">&nbsp;'."\n".'<tr><td>&nbsp;<td class="r">'.$timestamp.'<td class="r">'.$disp.'<td colspan="4">&nbsp;'."\n".'<tr><td colspan="3">&nbsp;'; // test ###
10222     }
10223     $tzoffset = array();
10224     // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates)
10225     $tzoffset['offsetHis'] = '+0000';
10226     $tzoffset['offsetSec'] = 0;
10227     $tzoffset['tzname']    = '?';
10228     if( !isset( $timezonesarray[$tzid] ))
10229       return $tzoffset;
10230     $tzdatearray = $timezonesarray[$tzid];
10231     if ( is_array($tzdatearray) ) {
10232         sort($tzdatearray); // just in case
10233         if ( $timestamp < $tzdatearray[0]['timestamp'] ) {
10234             // our date is before the first change
10235             $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ;
10236             $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ;
10237             $tzoffset['tzname']    = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case
10238         } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) {
10239             // our date is after the last change (we do this so our scan can stop at the last record but one)
10240             $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ;
10241             $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ;
10242             $tzoffset['tzname']    = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ;
10243         } else {
10244             // our date somewhere in between
10245             // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it
10246             // we don't include the last date in our loop as there isn't one after it to check
10247             for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) {
10248                 if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) {
10249                     $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ;
10250                     $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ;
10251                     $tzoffset['tzname']    = $tzdatearray[$i]['tzafter']['tzname'] ;
10252                     break;
10253                 }
10254             }
10255         }
10256     }
10257     return $tzoffset;
10258 }
10259 /**
10260  * Returns an array containing all the timezone data in the vcalendar object
10261  *
10262  * @param object $vcalendar, iCalcreator calendar instance
10263  * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
10264  *                based on the timezone data in the vcalendar object
10265  *
10266  */
10267 function getTimezonesAsDateArrays($vcalendar) {
10268     $timezonedata = array();
10269     while( $vtz = $vcalendar->getComponent( 'vtimezone' )) {
10270         $tzid       = $vtz->getProperty('tzid');
10271         $alltzdates = array();
10272         while ( $vtzc = $vtz->getComponent( 'standard' )) {
10273             $newtzdates = expandTimezoneDates($vtzc);
10274             $alltzdates = array_merge($alltzdates, $newtzdates);
10275         }
10276         while ( $vtzc = $vtz->getComponent( 'daylight' )) {
10277             $newtzdates = expandTimezoneDates($vtzc);
10278             $alltzdates = array_merge($alltzdates, $newtzdates);
10279         }
10280         sort($alltzdates);
10281         $timezonedata[$tzid] = $alltzdates;
10282     }
10283     return $timezonedata;
10284 }
10285 /**
10286  * Returns an array containing time zone data from vtimezone standard/daylight instances
10287  *
10288  * @param object $vtzc, an iCalcreator calendar standard/daylight instance
10289  * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
10290  *
10291  */
10292 function expandTimezoneDates($vtzc) {
10293     $tzdates = array();
10294     // prepare time zone "description" to attach to each change
10295     $tzbefore = array();
10296     $tzbefore['offsetHis']  = $vtzc->getProperty('tzoffsetfrom') ;
10297     $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']);
10298     if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 )))
10299       $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec'];
10300     $tzafter = array();
10301     $tzafter['offsetHis']   = $vtzc->getProperty('tzoffsetto') ;
10302     $tzafter['offsetSec']  = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']);
10303     if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 )))
10304       $tzafter['offsetSec'] = '+'.$tzafter['offsetSec'];
10305     if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname')))
10306       $tzafter['tzname'] = $tzafter['offsetHis'];
10307     // find out where to start from
10308     $dtstart = $vtzc->getProperty('dtstart');
10309     $dtstarttimestamp = mktime(
+10310             $dtstart['hour'],
+10311             $dtstart['min'],
+10312             $dtstart['sec'],
+10313             $dtstart['month'],
+10314             $dtstart['day'],
+10315             $dtstart['year']
+10316             ) ;
10317     if( !isset( $dtstart['unparsedtext'] )) // ??
10318       $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] );
10319     if ( $dtstarttimestamp == 0 ) {
10320         // it seems that the dtstart string may not have parsed correctly
10321         // let's set a timestamp starting from 1902, using the time part of the original string
10322         // so that the time will change at the right time of day
10323         // at worst we'll get midnight again
10324         $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ;
10325         $dtstarttimestamp = strtotime("19020101",0);
10326         $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp);
10327     }
10328     // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp
10329     $diff  = -1 * $tzbefore['offsetSec'];
10330     $dtstarttimestamp += $diff;
10331                 // add this (start) change to the array of changes
10332     $tzdates[] = array(
+10333         'timestamp' => $dtstarttimestamp,
+10334         'tzbefore'  => $tzbefore,
+10335         'tzafter'   => $tzafter
+10336         );
10337     $datearray = getdate($dtstarttimestamp);
10338     // save original array to use time parts, because strtotime (used below) apparently loses the time
10339     $changetime = $datearray ;
10340     // generate dates according to an RRULE line
10341     $rrule = $vtzc->getProperty('rrule') ;
10342     if ( is_array($rrule) ) {
10343         if ( $rrule['FREQ'] == 'YEARLY' ) {
10344             // calculate transition dates starting from DTSTART
10345             $offsetchangetimestamp = $dtstarttimestamp;
10346             // calculate transition dates until 10 years in the future
10347             $stoptimestamp = strtotime("+10 year",time());
10348             // if UNTIL is set, calculate until then (however far ahead)
10349             if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) {
10350                 $stoptimestamp = mktime(
+10351                     $rrule['UNTIL']['hour'],
+10352                     $rrule['UNTIL']['min'],
+10353                     $rrule['UNTIL']['sec'],
+10354                     $rrule['UNTIL']['month'],
+10355                     $rrule['UNTIL']['day'],
+10356                     $rrule['UNTIL']['year']
+10357                     ) ;
10358             }
10359             $count = 0 ;
10360             $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ;
10361             $daynames = array(
+10362                         'SU' => 'Sunday',
+10363                         'MO' => 'Monday',
+10364                         'TU' => 'Tuesday',
+10365                         'WE' => 'Wednesday',
+10366                         'TH' => 'Thursday',
+10367                         'FR' => 'Friday',
+10368                         'SA' => 'Saturday'
+10369                         );
10370             // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates
10371             while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) {
10372                 // break up the timestamp into its parts
10373                 $datearray = getdate($offsetchangetimestamp);
10374                 if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) {
10375                     // set the month
10376                     $datearray['mon'] = $rrule['BYMONTH'] ;
10377                 }
10378                 if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) {
10379                     // set specific day of month
10380                     $datearray['mday']  = $rrule['BYMONTHDAY'];
10381                 } elseif ( is_array($rrule['BYDAY']) ) {
10382                     // find the Xth WKDAY in the month
10383                     // the starting point for this process is the first of the month set above
10384                     $datearray['mday'] = 1 ;
10385                     // turn $datearray as it is now back into a timestamp
10386                     $offsetchangetimestamp = mktime(
+10387                         $datearray['hours'],
+10388                         $datearray['minutes'],
+10389                         $datearray['seconds'],
+10390                         $datearray['mon'],
+10391                         $datearray['mday'],
+10392                         $datearray['year']
+10393                             );
10394                     if ($rrule['BYDAY'][0] > 0) {
10395                         // to find Xth WKDAY in month, we find last WKDAY in month before
10396                         // we do that by finding first WKDAY in this month and going back one week
10397                         // then we add X weeks (below)
10398                         $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
10399                         $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp);
10400                     } else {
10401                         // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month
10402                         // we do that by going forward one month and going to WKDAY there
10403                         // then we subtract X weeks (below)
10404                         $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp);
10405                         $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
10406                     }
10407                     // now move forward or back the appropriate number of weeks, into the month we want
10408                     $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp);
10409                     $datearray = getdate($offsetchangetimestamp);
10410                 }
10411                 // convert the date parts back into a timestamp, setting the time parts according to the
10412                 // original time data which we stored
10413                 $offsetchangetimestamp = mktime(
+10414                     $changetime['hours'],
+10415                     $changetime['minutes'],
+10416                     $changetime['seconds'] + $diff,
+10417                     $datearray['mon'],
+10418                     $datearray['mday'],
+10419                     $datearray['year']
+10420                         );
10421                 // add this change to the array of changes
10422                 $tzdates[] = array(
+10423                     'timestamp' => $offsetchangetimestamp,
+10424                     'tzbefore'  => $tzbefore,
+10425                     'tzafter'   => $tzafter
+10426                     );
10427                 // update counters (timestamp and count)
10428                 $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp);
10429                 $count += 1 ;
10430             }
10431         }
10432     }
10433     // generate dates according to RDATE lines
10434     while ($rdates = $vtzc->getProperty('rdate')) {
10435         if ( is_array($rdates) ) {
10436 
10437             foreach ( $rdates as $rdate ) {
10438                 // convert the explicit change date to a timestamp
10439                 $offsetchangetimestamp = mktime(
+10440                         $rdate['hour'],
+10441                         $rdate['min'],
+10442                         $rdate['sec'] + $diff,
+10443                         $rdate['month'],
+10444                         $rdate['day'],
+10445                         $rdate['year']
+10446                         ) ;
10447                 // add this change to the array of changes
10448                 $tzdates[] = array(
+10449                     'timestamp' => $offsetchangetimestamp,
+10450                     'tzbefore'  => $tzbefore,
+10451                     'tzafter'   => $tzafter
+10452                     );
10453             }
10454         }
10455     }
10456     return $tzdates;
10457 }
10458 ?>
10459 <?php
10460 /*********************************************************************************/
10461 /**
10462  * iCalcreator v2.16.12
10463  * copyright (c) 2007-2013 Kjell-Inge Gustafsson kigkonsult
10464  * kigkonsult.se/iCalcreator/index.php
10465  * ical@kigkonsult.se
10466  *
10467  * Description:
10468  * This file is a PHP implementation of rfc2445/rfc5545.
10469  *
10470  * This library is free software; you can redistribute it and/or
10471  * modify it under the terms of the GNU Lesser General Public
10472  * License as published by the Free Software Foundation; either
10473  * version 2.1 of the License, or (at your option) any later version.
10474  *
10475  * This library is distributed in the hope that it will be useful,
10476  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10477  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10478  * Lesser General Public License for more details.
10479  *
10480  * You should have received a copy of the GNU Lesser General Public
10481  * License along with this library; if not, write to the Free Software
10482  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
10483  */
10484 /*********************************************************************************/
10485 /*********************************************************************************/
10486 /*         A little setup                                                        */
10487 /*********************************************************************************/
10488             /* your local language code */
10489 // define( 'ICAL_LANG', 'sv' );
10490             // alt. autosetting
10491 /*
10492 $langstr     = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
10493 $pos         = strpos( $langstr, ';' );
10494 if ($pos   !== false) {
10495   $langstr   = substr( $langstr, 0, $pos );
10496   $pos       = strpos( $langstr, ',' );
10497   if ($pos !== false) {
10498     $pos     = strpos( $langstr, ',' );
10499     $langstr = substr( $langstr, 0, $pos );
10500   }
10501   define( 'ICAL_LANG', $langstr );
10502 }
10503 */
10504 /*********************************************************************************/
10505 /*         version, do NOT remove!!                                              */
10506 define( 'ICALCREATOR_VERSION', 'iCalcreator 2.16.12' );
10507 /*********************************************************************************/
10508 /*********************************************************************************/
10509 /**
10510  * vcalendar class
10511  *
10512  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10513  * @since 2.9.6 - 2011-05-14
10514  */
10515 class vcalendar {
10516             //  calendar property variables
10517   var $calscale;
10518   var $method;
10519   var $prodid;
10520   var $version;
10521   var $xprop;
10522             //  container for calendar components
10523   var $components;
10524             //  component config variables
10525   var $allowEmpty;
10526   var $unique_id;
10527   var $language;
10528   var $directory;
10529   var $filename;
10530   var $url;
10531   var $delimiter;
10532   var $nl;
10533   var $format;
10534   var $dtzid;
10535             //  component internal variables
10536   var $attributeDelimiter;
10537   var $valueInit;
10538             //  component xCal declaration container
10539   var $xcaldecl;
10540 /**
10541  * constructor for calendar object
10542  *
10543  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10544  * @since 2.9.6 - 2011-05-14
10545  * @param array $config
10546  * @return void
10547  */
10548   function vcalendar ( $config = array()) {
10549     $this->_makeVersion();
10550     $this->calscale   = null;
10551     $this->method     = null;
10552     $this->_makeUnique_id();
10553     $this->prodid     = null;
10554     $this->xprop      = array();
10555     $this->language   = null;
10556     $this->directory  = null;
10557     $this->filename   = null;
10558     $this->url        = null;
10559     $this->dtzid      = null;
10560 /**
10561  *   language = <Text identifying a language, as defined in [RFC 1766]>
10562  */
10563     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
10564                                           $config['language']   = ICAL_LANG;
10565     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
10566     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
10567     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
10568     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
10569     $this->setConfig( $config );
10570 
10571     $this->xcaldecl   = array();
10572     $this->components = array();
10573   }
10574 /*********************************************************************************/
10575 /**
10576  * Property Name: CALSCALE
10577  */
10578 /**
10579  * creates formatted output for calendar property calscale
10580  *
10581  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10582  * @since 2.10.16 - 2011-10-28
10583  * @return string
10584  */
10585   function createCalscale() {
10586     if( empty( $this->calscale )) return FALSE;
10587     switch( $this->format ) {
10588       case 'xcal':
10589         return $this->nl.' calscale="'.$this->calscale.'"';
10590         break;
10591       default:
10592         return 'CALSCALE:'.$this->calscale.$this->nl;
10593         break;
10594     }
10595   }
10596 /**
10597  * set calendar property calscale
10598  *
10599  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10600  * @since 2.4.8 - 2008-10-21
10601  * @param string $value
10602  * @return void
10603  */
10604   function setCalscale( $value ) {
10605     if( empty( $value )) return FALSE;
10606     $this->calscale = $value;
10607   }
10608 /*********************************************************************************/
10609 /**
10610  * Property Name: METHOD
10611  */
10612 /**
10613  * creates formatted output for calendar property method
10614  *
10615  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10616  * @since 2.10.16 - 2011-10-28
10617  * @return string
10618  */
10619   function createMethod() {
10620     if( empty( $this->method )) return FALSE;
10621     switch( $this->format ) {
10622       case 'xcal':
10623         return $this->nl.' method="'.$this->method.'"';
10624         break;
10625       default:
10626         return 'METHOD:'.$this->method.$this->nl;
10627         break;
10628     }
10629   }
10630 /**
10631  * set calendar property method
10632  *
10633  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10634  * @since 2.4.8 - 2008-20-23
10635  * @param string $value
10636  * @return bool
10637  */
10638   function setMethod( $value ) {
10639     if( empty( $value )) return FALSE;
10640     $this->method = $value;
10641     return TRUE;
10642   }
10643 /*********************************************************************************/
10644 /**
10645  * Property Name: PRODID
10646  *
10647  *  The identifier is RECOMMENDED to be the identical syntax to the
10648  * [RFC 822] addr-spec. A good method to assure uniqueness is to put the
10649  * domain name or a domain literal IP address of the host on which.. .
10650  */
10651 /**
10652  * creates formatted output for calendar property prodid
10653  *
10654  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10655  * @since 2.12.11 - 2012-05-13
10656  * @return string
10657  */
10658   function createProdid() {
10659     if( !isset( $this->prodid ))
10660       $this->_makeProdid();
10661     switch( $this->format ) {
10662       case 'xcal':
10663         return $this->nl.' prodid="'.$this->prodid.'"';
10664         break;
10665       default:
10666         $toolbox = new calendarComponent();
10667         $toolbox->setConfig( $this->getConfig());
10668         return $toolbox->_createElement( 'PRODID', '', $this->prodid );
10669         break;
10670     }
10671   }
10672 /**
10673  * make default value for calendar prodid
10674  *
10675  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10676  * @since 2.6.8 - 2009-12-30
10677  * @return void
10678  */
10679   function _makeProdid() {
10680     $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
10681   }
10682 /**
10683  * Conformance: The property MUST be specified once in an iCalendar object.
10684  * Description: The vendor of the implementation SHOULD assure that this
10685  * is a globally unique identifier; using some technique such as an FPI
10686  * value, as defined in [ISO 9070].
10687  */
10688 /**
10689  * make default unique_id for calendar prodid
10690  *
10691  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10692  * @since 0.3.0 - 2006-08-10
10693  * @return void
10694  */
10695   function _makeUnique_id() {
10696     $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
10697   }
10698 /*********************************************************************************/
10699 /**
10700  * Property Name: VERSION
10701  *
10702  * Description: A value of "2.0" corresponds to this memo.
10703  */
10704 /**
10705  * creates formatted output for calendar property version
10706 
10707  *
10708  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10709  * @since 2.10.16 - 2011-10-28
10710  * @return string
10711  */
10712   function createVersion() {
10713     if( empty( $this->version ))
10714       $this->_makeVersion();
10715     switch( $this->format ) {
10716       case 'xcal':
10717         return $this->nl.' version="'.$this->version.'"';
10718         break;
10719       default:
10720         return 'VERSION:'.$this->version.$this->nl;
10721         break;
10722     }
10723   }
10724 /**
10725  * set default calendar version
10726  *
10727  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10728  * @since 0.3.0 - 2006-08-10
10729  * @return void
10730  */
10731   function _makeVersion() {
10732     $this->version = '2.0';
10733   }
10734 /**
10735  * set calendar version
10736  *
10737  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10738  * @since 2.4.8 - 2008-10-23
10739  * @param string $value
10740  * @return void
10741  */
10742   function setVersion( $value ) {
10743     if( empty( $value )) return FALSE;
10744     $this->version = $value;
10745     return TRUE;
10746   }
10747 /*********************************************************************************/
10748 /**
10749  * Property Name: x-prop
10750  */
10751 /**
10752  * creates formatted output for calendar property x-prop, iCal format only
10753  *
10754  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10755  * @since 2.16.2 - 2012-12-18
10756  * @return string
10757  */
10758   function createXprop() {
10759     if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
10760     $output = null;
10761     $toolbox = new calendarComponent();
10762     $toolbox->setConfig( $this->getConfig());
10763     foreach( $this->xprop as $label => $xpropPart ) {
10764       if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
10765         $output  .= $toolbox->_createElement( $label );
10766         continue;
10767       }
10768       $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
10769       if( is_array( $xpropPart['value'] )) {
10770         foreach( $xpropPart['value'] as $pix => $theXpart )
10771           $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->nl );
10772         $xpropPart['value']  = implode( ',', $xpropPart['value'] );
10773       }
10774       else
10775         $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
10776       $output    .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
10777       if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
10778         foreach( $toolbox->xcaldecl as $localxcaldecl )
10779           $this->xcaldecl[] = $localxcaldecl;
10780       }
10781     }
10782     return $output;
10783   }
10784 /**
10785  * set calendar property x-prop
10786  *
10787  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10788  * @since 2.11.9 - 2012-01-16
10789  * @param string $label
10790  * @param string $value
10791  * @param array $params optional
10792  * @return bool
10793  */
10794   function setXprop( $label, $value, $params=FALSE ) {
10795     if( empty( $label ))
10796       return FALSE;
10797     if( 'X-' != strtoupper( substr( $label, 0, 2 )))
10798       return FALSE;
10799     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
10800     $xprop           = array( 'value' => $value );
10801     $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
10802     if( !is_array( $this->xprop )) $this->xprop = array();
10803     $this->xprop[strtoupper( $label )] = $xprop;
10804     return TRUE;
10805   }
10806 /*********************************************************************************/
10807 /**
10808  * delete calendar property value
10809  *
10810  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10811  * @since 2.8.8 - 2011-03-15
10812  * @param mixed $propName, bool FALSE => X-property
10813  * @param int $propix, optional, if specific property is wanted in case of multiply occurences
10814  * @return bool, if successfull delete
10815  */
10816   function deleteProperty( $propName=FALSE, $propix=FALSE ) {
10817     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
10818     if( !$propix )
10819       $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
10820     $this->propdelix[$propName] = --$propix;
10821     $return = FALSE;
10822     switch( $propName ) {
10823       case 'CALSCALE':
10824         if( isset( $this->calscale )) {
10825           $this->calscale = null;
10826           $return = TRUE;
10827         }
10828         break;
10829       case 'METHOD':
10830         if( isset( $this->method )) {
10831           $this->method   = null;
10832           $return = TRUE;
10833         }
10834         break;
10835       default:
10836         $reduced = array();
10837         if( $propName != 'X-PROP' ) {
10838           if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
10839           foreach( $this->xprop as $k => $a ) {
10840             if(( $k != $propName ) && !empty( $a ))
10841               $reduced[$k] = $a;
10842           }
10843         }
10844         else {
10845           if( count( $this->xprop ) <= $propix )  return FALSE;
10846           $xpropno = 0;
10847           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
10848             if( $propix != $xpropno )
10849               $reduced[$xpropkey] = $xpropvalue;
10850             $xpropno++;
10851           }
10852         }
10853         $this->xprop = $reduced;
10854         if( empty( $this->xprop )) {
10855           unset( $this->propdelix[$propName] );
10856           return FALSE;
10857         }
10858         return TRUE;
10859     }
10860     return $return;
10861   }
10862 /**
10863  * get calendar property value/params
10864  *
10865  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10866  * @since 2.13.4 - 2012-08-08
10867  * @param string $propName, optional
10868  * @param int $propix, optional, if specific property is wanted in case of multiply occurences
10869  * @param bool $inclParam=FALSE
10870  * @return mixed
10871  */
10872   function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
10873     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
10874     if( 'X-PROP' == $propName ) {
10875       if( !$propix )
10876         $propix  = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
10877       $this->propix[$propName] = --$propix;
10878     }
10879     else
10880       $mProps    = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
10881     switch( $propName ) {
10882       case 'ATTENDEE':
10883       case 'CATEGORIES':
10884       case 'CONTACT':
10885       case 'DTSTART':
10886       case 'GEOLOCATION':
10887       case 'LOCATION':
10888       case 'ORGANIZER':
10889       case 'PRIORITY':
10890       case 'RESOURCES':
10891       case 'STATUS':
10892       case 'SUMMARY':
10893       case 'RECURRENCE-ID-UID':
10894       case 'RELATED-TO':
10895       case 'R-UID':
10896       case 'UID':
10897       case 'URL':
10898         $output  = array();
10899         foreach ( $this->components as $cix => $component) {
10900           if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
10901             continue;
10902           if( in_array( strtoupper( $propName ), $mProps )) {
10903             $component->_getProperties( $propName, $output );
10904             continue;
10905           }
10906           elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
10907             if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
10908               $content = $component->getProperty( 'UID' );
10909           }
10910           elseif( 'GEOLOCATION' == $propName ) {
10911             $content = $component->getProperty( 'LOCATION' );
10912             $content = ( !empty( $content )) ? $content.' ' : '';
10913             if(( FALSE === ( $geo     = $component->getProperty( 'GEO' ))) || empty( $geo ))
10914               continue;
10915             if( 0.0 < $geo['latitude'] )
10916               $sign   = '+';
10917             else
10918               $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
10919             $content .= ' '.$sign.sprintf( "%09.6f", abs( $geo['latitude'] ));
10920             $content  = rtrim( rtrim( $content, '0' ), '.' );
10921             if( 0.0 < $geo['longitude'] )
10922               $sign   = '+';
10923             else
10924               $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
10925             $content .= $sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';
10926           }
10927           elseif( FALSE === ( $content = $component->getProperty( $propName )))
10928             continue;
10929           if(( FALSE === $content ) || empty( $content ))
10930             continue;
10931           elseif( is_array( $content )) {
10932             if( isset( $content['year'] )) {
10933               $key  = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
10934               if( !isset( $output[$key] ))
10935                 $output[$key] = 1;
10936               else
10937                 $output[$key] += 1;
10938             }
10939             else {
10940               foreach( $content as $partValue => $partCount ) {
10941                 if( !isset( $output[$partValue] ))
10942                   $output[$partValue] = $partCount;
10943                 else
10944                   $output[$partValue] += $partCount;
10945               }
10946             }
10947           } // end elseif( is_array( $content )) {
10948           elseif( !isset( $output[$content] ))
10949             $output[$content] = 1;
10950           else
10951             $output[$content] += 1;
10952         } // end foreach ( $this->components as $cix => $component)
10953         if( !empty( $output ))
10954           ksort( $output );
10955         return $output;
10956         break;
10957       case 'CALSCALE':
10958         return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
10959         break;
10960       case 'METHOD':
10961         return ( !empty( $this->method )) ? $this->method : FALSE;
10962         break;
10963       case 'PRODID':
10964         if( empty( $this->prodid ))
10965           $this->_makeProdid();
10966         return $this->prodid;
10967         break;
10968       case 'VERSION':
10969         return ( !empty( $this->version )) ? $this->version : FALSE;
10970         break;
10971       default:
10972         if( $propName != 'X-PROP' ) {
10973           if( !isset( $this->xprop[$propName] )) return FALSE;
10974           return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
10975                                 : array( $propName, $this->xprop[$propName]['value'] );
10976         }
10977         else {
10978           if( empty( $this->xprop )) return FALSE;
10979           $xpropno = 0;
10980           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
10981             if( $propix == $xpropno )
10982               return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
10983                                     : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
10984             else
10985               $xpropno++;
10986           }
10987           unset( $this->propix[$propName] );
10988           return FALSE; // not found ??
10989         }
10990     }
10991     return FALSE;
10992   }
10993 /**
10994  * general vcalendar property setting
10995  *
10996  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10997  * @since 2.2.13 - 2007-11-04
10998  * @param mixed $args variable number of function arguments,
10999  *                    first argument is ALWAYS component name,
11000  *                    second ALWAYS component value!
11001  * @return bool
11002  */
11003   function setProperty () {
11004     $numargs    = func_num_args();
11005     if( 1 > $numargs )
11006       return FALSE;
11007     $arglist    = func_get_args();
11008     $arglist[0] = strtoupper( $arglist[0] );
11009     switch( $arglist[0] ) {
11010       case 'CALSCALE':
11011         return $this->setCalscale( $arglist[1] );
11012       case 'METHOD':
11013         return $this->setMethod( $arglist[1] );
11014       case 'VERSION':
11015         return $this->setVersion( $arglist[1] );
11016       default:
11017         if( !isset( $arglist[1] )) $arglist[1] = null;
11018         if( !isset( $arglist[2] )) $arglist[2] = null;
11019         return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
11020     }
11021     return FALSE;
11022   }
11023 /*********************************************************************************/
11024 /**
11025  * get vcalendar config values or * calendar components
11026  *
11027  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11028  * @since 2.11.7 - 2012-01-12
11029  * @param mixed $config
11030  * @return value
11031  */
11032   function getConfig( $config = FALSE ) {
11033     if( !$config ) {
11034       $return = array();
11035       $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
11036       $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
11037       $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
11038       $return['FILENAME']    = $this->getConfig( 'FILENAME' );
11039       $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
11040       $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
11041       $return['FORMAT']      = $this->getConfig( 'FORMAT' );
11042       if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
11043         $return['LANGUAGE']  = $lang;
11044       $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
11045       $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
11046       if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
11047         $return['URL']       = $url;
11048       $return['TZID']        = $this->getConfig( 'TZID' );
11049       return $return;
11050     }
11051     switch( strtoupper( $config )) {
11052       case 'ALLOWEMPTY':
11053         return $this->allowEmpty;
11054         break;
11055       case 'COMPSINFO':
11056         unset( $this->compix );
11057         $info = array();
11058         foreach( $this->components as $cix => $component ) {
11059           if( empty( $component )) continue;
11060           $info[$cix]['ordno'] = $cix + 1;
11061           $info[$cix]['type']  = $component->objName;
11062           $info[$cix]['uid']   = $component->getProperty( 'uid' );
11063           $info[$cix]['props'] = $component->getConfig( 'propinfo' );
11064           $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
11065         }
11066         return $info;
11067         break;
11068       case 'DELIMITER':
11069         return $this->delimiter;
11070         break;
11071       case 'DIRECTORY':
11072         if( empty( $this->directory ) && ( '0' != $this->directory ))
11073           $this->directory = '.';
11074         return $this->directory;
11075         break;
11076       case 'DIRFILE':
11077         return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
11078         break;
11079       case 'FILEINFO':
11080         return array( $this->getConfig( 'directory' )
+11081                     , $this->getConfig( 'filename' )
+11082                     , $this->getConfig( 'filesize' ));
11083         break;
11084       case 'FILENAME':
11085         if( empty( $this->filename ) && ( '0' != $this->filename )) {
11086           if( 'xcal' == $this->format )
11087             $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
11088           else
11089             $this->filename = date( 'YmdHis' ).'.ics';
11090         }
11091         return $this->filename;
11092         break;
11093       case 'FILESIZE':
11094         $size    = 0;
11095         if( empty( $this->url )) {
11096           $dirfile = $this->getConfig( 'dirfile' );
11097           if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
11098             $size = 0;
11099           clearstatcache();
11100         }
11101         return $size;
11102         break;
11103       case 'FORMAT':
11104         return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
11105         break;
11106       case 'LANGUAGE':
11107          /* get language for calendar component as defined in [RFC 1766] */
11108         return $this->language;
11109         break;
11110       case 'NL':
11111       case 'NEWLINECHAR':
11112         return $this->nl;
11113         break;
11114       case 'TZID':
11115         return $this->dtzid;
11116         break;
11117       case 'UNIQUE_ID':
11118         return $this->unique_id;
11119         break;
11120       case 'URL':
11121         if( !empty( $this->url ))
11122           return $this->url;
11123         else
11124           return FALSE;
11125         break;
11126     }
11127   }
11128 /**
11129  * general vcalendar config setting
11130  *
11131  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11132  * @since 2.16.7 - 2013-01-11
11133  * @param mixed  $config
11134  * @param string $value
11135  * @return void
11136  */
11137   function setConfig( $config, $value = FALSE) {
11138     if( is_array( $config )) {
11139       $ak = array_keys( $config );
11140       foreach( $ak as $k ) {
11141         if( 'DIRECTORY' == strtoupper( $k )) {
11142           if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
11143             return FALSE;
11144           unset( $config[$k] );
11145         }
11146         elseif( 'NEWLINECHAR' == strtoupper( $k )) {
11147           if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
11148             return FALSE;
11149           unset( $config[$k] );
11150         }
11151       }
11152       foreach( $config as $cKey => $cValue ) {
11153         if( FALSE === $this->setConfig( $cKey, $cValue ))
11154           return FALSE;
11155       }
11156       return TRUE;
11157     }
11158     $res = FALSE;
11159     switch( strtoupper( $config )) {
11160       case 'ALLOWEMPTY':
11161         $this->allowEmpty = $value;
11162         $subcfg  = array( 'ALLOWEMPTY' => $value );
11163         $res = TRUE;
11164         break;
11165       case 'DELIMITER':
11166         $this->delimiter = $value;
11167         return TRUE;
11168         break;
11169       case 'DIRECTORY':
11170         $value   = trim( $value );
11171         $del     = $this->getConfig('delimiter');
11172         if( $del == substr( $value, ( 0 - strlen( $del ))))
11173           $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
11174         if( is_dir( $value )) {
11175             /* local directory */
11176           clearstatcache();
11177           $this->directory = $value;
11178           $this->url       = null;
11179           return TRUE;
11180         }
11181         else
11182           return FALSE;
11183         break;
11184       case 'FILENAME':
11185         $value   = trim( $value );
11186         if( !empty( $this->url )) {
11187             /* remote directory+file -> URL */
11188           $this->filename = $value;
11189           return TRUE;
11190         }
11191         $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
11192         if( file_exists( $dirfile )) {
11193             /* local file exists */
11194           if( is_readable( $dirfile ) || is_writable( $dirfile )) {
11195             clearstatcache();
11196             $this->filename = $value;
11197             return TRUE;
11198           }
11199           else
11200             return FALSE;
11201         }
11202         elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
11203             /* read- or writable directory */
11204           $this->filename = $value;
11205           return TRUE;
11206         }
11207         else
11208           return FALSE;
11209         break;
11210       case 'FORMAT':
11211         $value   = trim( strtolower( $value ));
11212         if( 'xcal' == $value ) {
11213           $this->format             = 'xcal';
11214           $this->attributeDelimiter = $this->nl;
11215           $this->valueInit          = null;
11216         }
11217         else {
11218           $this->format             = null;
11219           $this->attributeDelimiter = ';';
11220           $this->valueInit          = ':';
11221         }
11222         $subcfg  = array( 'FORMAT' => $value );
11223         $res = TRUE;
11224         break;
11225       case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766]
11226         $value   = trim( $value );
11227         $this->language = $value;
11228         $this->_makeProdid();
11229         $subcfg  = array( 'LANGUAGE' => $value );
11230         $res = TRUE;
11231         break;
11232       case 'NL':
11233       case 'NEWLINECHAR':
11234         $this->nl = $value;
11235         if( 'xcal' == $value ) {
11236           $this->attributeDelimiter = $this->nl;
11237           $this->valueInit          = null;
11238         }
11239         else {
11240           $this->attributeDelimiter = ';';
11241           $this->valueInit          = ':';
11242         }
11243         $subcfg  = array( 'NL' => $value );
11244         $res = TRUE;
11245         break;
11246       case 'TZID':
11247         $this->dtzid = $value;
11248         $subcfg  = array( 'TZID' => $value );
11249         $res = TRUE;
11250         break;
11251       case 'UNIQUE_ID':
11252         $value   = trim( $value );
11253         $this->unique_id = $value;
11254         $this->_makeProdid();
11255         $subcfg  = array( 'UNIQUE_ID' => $value );
11256         $res = TRUE;
11257         break;
11258       case 'URL':
11259             /* remote file - URL */
11260         $value     = str_replace( array( 'HTTP://', 'WEBCAL://', 'webcal://' ), 'http://', trim( $value ));
11261         if( 'http://' != substr( $value, 0, 7 ))
11262           return FALSE;
11263         $s1        = $this->url;
11264         $this->url = $value;
11265         $s2        = $this->directory;
11266         $this->directory = null;
11267         $parts     = pathinfo( $value );
11268         if( FALSE === $this->setConfig( 'filename',  $parts['basename'] )) {
11269           $this->url       = $s1;
11270           $this->directory = $s2;
11271           return FALSE;
11272         }
11273         else
11274           return TRUE;
11275         break;
11276       default:  // any unvalid config key.. .
11277         return TRUE;
11278     }
11279     if( !$res ) return FALSE;
11280     if( isset( $subcfg ) && !empty( $this->components )) {
11281       foreach( $subcfg as $cfgkey => $cfgvalue ) {
11282         foreach( $this->components as $cix => $component ) {
11283           $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
11284           if( !$res )
11285             break 2;
11286           $this->components[$cix] = $component->copy(); // PHP4 compliant
11287         }
11288       }
11289     }
11290     return $res;
11291   }
11292 /*********************************************************************************/
11293 /**
11294  * add calendar component to container
11295  *
11296  * alias to setComponent
11297  *
11298  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11299  * @since 1.x.x - 2007-04-24
11300  * @param object $component calendar component
11301  * @return void
11302  */
11303   function addComponent( $component ) {
11304     $this->setComponent( $component );
11305   }
11306 /**
11307  * delete calendar component from container
11308  *
11309  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11310  * @since 2.8.8 - 2011-03-15
11311  * @param mixed $arg1 ordno / component type / component uid
11312  * @param mixed $arg2 optional, ordno if arg1 = component type
11313  * @return void
11314  */
11315   function deleteComponent( $arg1, $arg2=FALSE  ) {
11316     $argType = $index = null;
11317     if ( ctype_digit( (string) $arg1 )) {
11318       $argType = 'INDEX';
11319       $index   = (int) $arg1 - 1;
11320     }
11321     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
11322       $argType = strtolower( $arg1 );
11323       $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
11324     }
11325     $cix1dC = 0;
11326     foreach ( $this->components as $cix => $component) {
11327       if( empty( $component )) continue;
11328       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
11329         unset( $this->components[$cix] );
11330         return TRUE;
11331       }
11332       elseif( $argType == $component->objName ) {
11333         if( $index == $cix1dC ) {
11334           unset( $this->components[$cix] );
11335           return TRUE;
11336         }
11337         $cix1dC++;
11338       }
11339       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
11340         unset( $this->components[$cix] );
11341         return TRUE;
11342       }
11343     }
11344     return FALSE;
11345   }
11346 /**
11347  * get calendar component from container
11348  *
11349  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11350  * @since 2.13.5 - 2012-08-08
11351  * @param mixed $arg1 optional, ordno/component type/ component uid
11352  * @param mixed $arg2 optional, ordno if arg1 = component type
11353  * @return object
11354  */
11355   function getComponent( $arg1=FALSE, $arg2=FALSE ) {
11356     $index = $argType = null;
11357     if ( !$arg1 ) { // first or next in component chain
11358       $argType = 'INDEX';
11359       $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
11360     }
11361     elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
11362       $argType = 'INDEX';
11363       $index   = (int) $arg1;
11364       unset( $this->compix );
11365     }
11366     elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
11367       $arg2  = implode( '-', array_keys( $arg1 ));
11368       $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
11369       $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
11370       $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
11371       $mProps     = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
11372     }
11373     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
11374       unset( $this->compix['INDEX'] );
11375       $argType = strtolower( $arg1 );
11376       if( !$arg2 )
11377         $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
11378       elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
11379         $index = (int) $arg2;
11380     }
11381     elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
11382       if( !$arg2 )
11383         $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
11384       elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
11385         $index = (int) $arg2;
11386     }
11387     if( isset( $index ))
11388       $index  -= 1;
11389     $ckeys = array_keys( $this->components );
11390     if( !empty( $index) && ( $index > end(  $ckeys )))
11391       return FALSE;
11392     $cix1gC = 0;
11393     foreach ( $this->components as $cix => $component) {
11394       if( empty( $component )) continue;
11395       if(( 'INDEX' == $argType ) && ( $index == $cix ))
11396         return $component->copy();
11397       elseif( $argType == $component->objName ) {
11398         if( $index == $cix1gC )
11399           return $component->copy();
11400         $cix1gC++;
11401       }
11402       elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
11403         $hit = array();
11404         foreach( $arg1 as $pName => $pValue ) {
11405           $pName = strtoupper( $pName );
11406           if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
11407             continue;
11408           if( in_array( $pName, $mProps )) { // multiple occurrence
11409             $propValues = array();
11410             $component->_getProperties( $pName, $propValues );
11411             $propValues = array_keys( $propValues );
11412             $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
11413             continue;
11414           } // end   if(.. .// multiple occurrence
11415           if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence
11416             $hit[] = FALSE; // missing property
11417             continue;
11418           }
11419           if( 'SUMMARY' == $pName ) { // exists within (any case)
11420             $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE;
11421             continue;
11422           }
11423           if( in_array( strtoupper( $pName ), $dateProps )) {
11424             $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
11425             if( 8 < strlen( $pValue )) {
11426               if( isset( $value['hour'] )) {
11427                 if( 'T' == substr( $pValue, 8, 1 ))
11428                   $pValue = str_replace( 'T', '', $pValue );
11429                 $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
11430               }
11431               else
11432                 $pValue = substr( $pValue, 0, 8 );
11433             }
11434             $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE;
11435             continue;
11436           }
11437           elseif( !is_array( $value ))
11438             $value = array( $value );
11439           foreach( $value as $part ) {
11440             $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
11441             foreach( $part as $subPart ) {
11442               if( $pValue == $subPart ) {
11443                 $hit[] = TRUE;
11444                 continue 3;
11445               }
11446             }
11447           } // end foreach( $value as $part )
11448           $hit[] = FALSE; // no hit in property
11449         } // end  foreach( $arg1 as $pName => $pValue )
11450         if( in_array( TRUE, $hit )) {
11451           if( $index == $cix1gC )
11452             return $component->copy();
11453           $cix1gC++;
11454         }
11455       } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
11456       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
11457         if( $index == $cix1gC )
11458           return $component->copy();
11459         $cix1gC++;
11460       }
11461     } // end foreach ( $this->components.. .
11462             /* not found.. . */
11463     unset( $this->compix );
11464     return FALSE;
11465   }
11466 /**
11467  * create new calendar component, already included within calendar
11468  *
11469  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11470  * @since 2.6.33 - 2011-01-03
11471  * @param string $compType component type
11472  * @return object (reference)
11473  */
11474   function & newComponent( $compType ) {
11475     $config = $this->getConfig();
11476     $keys   = array_keys( $this->components );
11477     $ix     = end( $keys) + 1;
11478     switch( strtoupper( $compType )) {
11479       case 'EVENT':
11480       case 'VEVENT':
11481         $this->components[$ix] = new vevent( $config );
11482         break;
11483       case 'TODO':
11484       case 'VTODO':
11485         $this->components[$ix] = new vtodo( $config );
11486         break;
11487       case 'JOURNAL':
11488       case 'VJOURNAL':
11489         $this->components[$ix] = new vjournal( $config );
11490         break;
11491       case 'FREEBUSY':
11492       case 'VFREEBUSY':
11493         $this->components[$ix] = new vfreebusy( $config );
11494         break;
11495       case 'TIMEZONE':
11496       case 'VTIMEZONE':
11497         array_unshift( $this->components, new vtimezone( $config ));
11498         $ix = 0;
11499         break;
11500       default:
11501         return FALSE;
11502     }
11503     return $this->components[$ix];
11504   }
11505 /**
11506  * select components from calendar on date or selectOption basis
11507  *
11508  * Ensure DTSTART is set for every component.
11509  * No date controls occurs.
11510  *
11511  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11512  * @since 2.16.12 - 2013-02-10
11513  * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
11514  * @param int   $startM optional, start Month, default current Month
11515  * @param int   $startD optional, start Day,   default current Day
11516  * @param int   $endY   optional, end   Year,  default $startY
11517  * @param int   $endY   optional, end   Month, default $startM
11518  * @param int   $endY   optional, end   Day,   default $startD
11519  * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
11520  * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
11521  *                                TRUE            => output : array[] (ignores split)
11522  * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
11523  *                                FALSE          - only component(-s) that starts within period
11524  * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
11525  *                                                 period (implies flat=FALSE)
11526  *                                FALSE          - one occurance of component only in output array
11527  * @return array or FALSE
11528  */
11529   function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
11530             /* check  if empty calendar */
11531     if( 0 >= count( $this->components )) return FALSE;
11532     if( is_array( $startY ))
11533       return $this->selectComponents2( $startY );
11534             /* check default dates */
11535     if( !$startY ) $startY = date( 'Y' );
11536     if( !$startM ) $startM = date( 'm' );
11537     if( !$startD ) $startD = date( 'd' );
11538     $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
11539     if( !$endY )   $endY   = $startY;
11540     if( !$endM )   $endM   = $startM;
11541     if( !$endD )   $endD   = $startD;
11542     $endDate   = mktime( 23, 59, 59, $endM, $endD, $endY );
11543 // echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br />\n"; $tcnt = 0;// test ###
11544             /* check component types */
11545     $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
11546     if( is_array( $cType )) {
11547       foreach( $cType as $cix => $theType ) {
11548         $cType[$cix] = $theType = strtolower( $theType );
11549         if( !in_array( $theType, $validTypes ))
11550           $cType[$cix] = 'vevent';
11551       }
11552       $cType = array_unique( $cType );
11553     }
11554     elseif( !empty( $cType )) {
11555       $cType = strtolower( $cType );
11556       if( !in_array( $cType, $validTypes ))
11557         $cType = array( 'vevent' );
11558       else
11559         $cType = array( $cType );
11560     }
11561     else
11562       $cType = $validTypes;
11563     if( 0 >= count( $cType ))
11564       $cType = $validTypes;
11565     if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
11566       $split = FALSE;
11567     if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
11568       $split = FALSE;
11569             /* iterate components */
11570     $result       = array();
11571     $this->sort( 'UID' );
11572     $compUIDcmp   = null;
11573     $recurridList = array();
11574     foreach ( $this->components as $cix => $component ) {
11575       if( empty( $component )) continue;
11576       unset( $start );
11577             /* deselect unvalid type components */
11578       if( !in_array( $component->objName, $cType ))
11579         continue;
11580       $start = $component->getProperty( 'dtstart' );
11581             /* select due when dtstart is missing */
11582       if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' ))))
11583         continue;
11584       if( empty( $start ))
11585         continue;
11586       $compUID      = $component->getProperty( 'UID' );
11587       if( $compUIDcmp != $compUID ) {
11588         $compUIDcmp = $compUID;
11589         unset( $exdatelist, $recurridList );
11590       }
11591       $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE;
11592       unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $workstart, $workend, $endDateFormat ); // clean up
11593       $startWdate = iCalUtilityFunctions::_date2timestamp( $start );
11594       $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
11595             /* get end date from dtend/due/duration properties */
11596       $end = $component->getProperty( 'dtend' );
11597       if( !empty( $end )) {
11598         $dtendExist = TRUE;
11599         $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
11600       }
11601       if( empty( $end ) && ( $component->objName == 'vtodo' )) {
11602         $end = $component->getProperty( 'due' );
11603         if( !empty( $end )) {
11604           $dueExist = TRUE;
11605           $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
11606         }
11607       }
11608       if( !empty( $end ) && !isset( $end['hour'] )) {
11609           /* a DTEND without time part regards an event that ends the day before,
11610              for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
11611         $endAllDayEvent = TRUE;
11612         $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
11613         $end['year']  = date( 'Y', $endWdate );
11614         $end['month'] = date( 'm', $endWdate );
11615         $end['day']   = date( 'd', $endWdate );
11616         $end['hour']  = 23;
11617         $end['min']   = $end['sec'] = 59;
11618       }
11619       if( empty( $end )) {
11620         $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
11621         if( !empty( $end ))
11622           $durationExist = TRUE;
11623           $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
11624 // if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
11625       }
11626       if( empty( $end )) { // assume one day duration if missing end date
11627         $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
11628       }
11629 // if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
11630       $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
11631       if( $endWdate < $startWdate ) { // MUST be after start date!!
11632         $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
11633         $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
11634       }
11635       $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds
11636             /* make a list of optional exclude dates for component occurence from exrule and exdate */
11637       $exdatelist = array();
11638       $workstart  = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
11639       $workend    = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
11640       while( FALSE !== ( $exrule = $component->getProperty( 'exrule' )))    // check exrule
11641         iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
11642       while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) {  // check exdate
11643         foreach( $exdate as $theExdate ) {
11644           $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
11645           $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
11646           if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
11647             $exdatelist[$exWdate] = TRUE;
11648         } // end - foreach( $exdate as $theExdate )
11649       }  // end - check exdate
11650             /* check recurrence-id (note, a missing sequence=0, don't test foer sequence), remove hit with reccurr-id date */
11651       if( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) {
11652 // echo "adding ".$recurrid['year'].'-'.$recurrid['month'].'-'.$recurrid['day']." to recurridList<br>\n"; // test ###
11653         $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
11654         $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
11655         $recurridList[$recurrid] = TRUE; // no recurring to start this day
11656       } // end recurrence-id/sequence test
11657             /* select only components with.. . */
11658       if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
+11659          (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period
11660             /* add the selected component (WITHIN valid dates) to output array */
11661         if( $flat ) { // any=true/false, ignores split
11662           if( !$recurrid )
11663             $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
11664         }
11665         elseif( $split ) { // split the original component
11666           if( $endWdate > $endDate )
11667             $endWdate = $endDate;     // use period end date
11668           $rstart   = $startWdate;
11669           if( $rstart < $startDate )
11670             $rstart = $startDate; // use period start date
11671           $startYMD = $rstartYMD = date( 'Ymd', $rstart );
11672           $endYMD   = date( 'Ymd', $endWdate );
11673           $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
11674 // echo "start org comp = $rstartYMD, endYMD=$endYMD<br />\n"; // test ###
11675           if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
11676             while( $rstartYMD <= $endYMD ) { // iterate
11677               if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
11678                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
11679                 $rstartYMD = date( 'Ymd', $rstart );
11680                 continue;
11681               }
11682               if( $rstartYMD > $startYMD ) // date after dtstart
11683                 $datestring = date( $startDateFormat, $checkDate ); // mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
11684               else
11685                 $datestring = date( $startDateFormat, $rstart );
11686               if( isset( $start['tz'] ))
11687                 $datestring .= ' '.$start['tz'];
11688 // echo "split org comp rstartYMD=$rstartYMD (datestring=$datestring)<br />\n"; // test ###
11689               $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
11690               if( $dtendExist || $dueExist || $durationExist ) {
11691                 if( $rstartYMD < $endYMD ) // not the last day
11692                   $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
11693                 else
11694                   $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
11695                 if( $endAllDayEvent && $dtendExist )
11696                   $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
11697                 $datestring = date( $endDateFormat, $tend );
11698                 if( isset( $end['tz'] ))
11699                   $datestring .= ' '.$end['tz'];
11700                 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
11701                 $component->setProperty( $propName, $datestring );
11702               } // end if( $dtendExist || $dueExist || $durationExist )
11703               $wd        = getdate( $rstart );
11704               $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
11705               $rstart    = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
11706               $rstartYMD = date( 'Ymd', $rstart );
11707               $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
11708             } // end while( $rstart <= $endWdate )
11709           }
11710         } // end elseif( $split )   -  else use component date
11711         elseif( $recurrid && !$flat && !$any && !$split )
11712           $continue = TRUE;
11713         else { // !$flat && !$split, i.e. no flat array and DTSTART within period
11714           $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
11715           if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
11716             $wd = getdate( $startWdate );
11717             $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
11718           }
11719         }
11720       } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
11721             /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
11722       if( TRUE === $any ) {
11723             /* make a list of optional repeating dates for component occurence, rrule, rdate */
11724         $recurlist = array();
11725         while( FALSE !== ( $rrule = $component->getProperty( 'rrule' )))    // check rrule
11726           iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
11727         foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
11728           $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
11729         while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) {  // check rdate
11730           foreach( $rdate as $theRdate ) {
11731             if( is_array( $theRdate ) && ( 2 == count( $theRdate )) &&  // all days within PERIOD
11732                    array_key_exists( '0', $theRdate ) &&  array_key_exists( '1', $theRdate )) {
11733               $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
11734               if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
11735                 continue;
11736               if( isset( $theRdate[1]['year'] )) // date-date period
11737                 $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
11738               else {                             // date-duration period
11739                 $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
11740                 $rend = iCalUtilityFunctions::_date2timestamp( $rend );
11741               }
11742               while( $rstart < $rend ) {
11743                 $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
11744                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
11745               }
11746             } // PERIOD end
11747             else { // single date
11748               $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
11749               if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
11750                 $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
11751             }
11752           }
11753         }  // end - check rdate
11754         foreach( $recurlist as $recurkey => $durvalue ) { // remove all recurrence START dates found in the exdatelist
11755           $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
11756           if( isset( $exdatelist[$checkDate] )) // no recurring to start this day
11757             unset( $recurlist[$recurkey] );
11758         }
11759         if( 0 < count( $recurlist )) {
11760           ksort( $recurlist );
11761           $xRecurrence = 1;
11762           $component2  = $component->copy();
11763           $compUID     = $component2->getProperty( 'UID' );
11764           foreach( $recurlist as $recurkey => $durvalue ) {
11765 // echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###;
11766             if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
11767               continue;
11768             $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
11769             if( isset( $recurridList[$checkDate] )) // no recurring to start this day
11770               continue;
11771             if( isset( $exdatelist[$checkDate] )) // check excluded dates
11772               continue;
11773             if( $startWdate >= $recurkey ) // exclude component start date
11774               continue;
11775             $rstart = $recurkey;
11776             $rend   = $recurkey + $durvalue;
11777            /* add repeating components within valid dates to output array, only start date set */
11778             if( $flat ) {
11779               if( !isset( $result[$compUID] )) // only one comp
11780                 $result[$compUID] = $component2->copy(); // copy to output
11781             }
11782            /* add repeating components within valid dates to output array, one each day */
11783             elseif( $split ) {
11784               $xRecurrence += 1;
11785               if( $rend > $endDate )
11786                 $rend = $endDate;
11787               $startYMD = $rstartYMD = date( 'Ymd', $rstart );
11788               $endYMD   = date( 'Ymd', $rend );
11789 // echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br />\n"; // test ###;
11790               while( $rstart <= $rend ) { // iterate.. .
11791                 $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
11792                 if( isset( $recurridList[$checkDate] )) // no recurring to start this day
11793                   break;
11794                 if( isset( $exdatelist[$checkDate] ))  // exclude any recurrence START date, found in exdatelist
11795                   break;
11796 // echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br />"; // test ###;
11797                 if( $rstart >= $startDate ) {    // date after dtstart
11798                   if( $rstartYMD > $startYMD ) // date after dtstart
11799                     $datestring = date( $startDateFormat, $checkDate );
11800                   else
11801                     $datestring = date( $startDateFormat, $rstart );
11802                   if( isset( $start['tz'] ))
11803                     $datestring .= ' '.$start['tz'];
11804 // echo "spliting = $datestring<BR>\n"; // test ###
11805                   $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
11806                   if( $dtendExist || $dueExist || $durationExist ) {
11807                     if( $rstartYMD < $endYMD ) // not the last day
11808                       $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
11809                     else
11810                       $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
11811                     if( $endAllDayEvent && $dtendExist )
11812                       $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
11813                     $datestring = date( $endDateFormat, $tend );
11814                     if( isset( $end['tz'] ))
11815                       $datestring .= ' '.$end['tz'];
11816                     $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
11817                     $component2->setProperty( $propName, $datestring );
11818                   } // end if( $dtendExist || $dueExist || $durationExist )
11819                   $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
11820                   $wd = getdate( $rstart );
11821                   $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
11822                 } // end if( $checkDate > $startYMD ) {    // date after dtstart
11823                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
11824                 $rstartYMD = date( 'Ymd', $rstart );
11825               } // end while( $rstart <= $rend )
11826             } // end elseif( $split )
11827             elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *//
11828               $xRecurrence += 1;
11829               $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
11830               if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
11831                 $datestring = date( $startDateFormat, $rstart );
11832                 if( isset( $start['tz'] ))
11833                   $datestring .= ' '.$start['tz'];
11834 //echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
11835                 $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
11836                 if( $dtendExist || $dueExist || $durationExist ) {
11837                   $tend = $rstart + $rdurWsecs;
11838                   if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate ))
11839                     $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ));
11840                   else
11841                     $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!!
11842                   if( $endAllDayEvent && $dtendExist )
11843                     $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
11844                   $datestring = date( $endDateFormat, $tend );
11845                   if( isset( $end['tz'] ))
11846                     $datestring .= ' '.$end['tz'];
11847                   $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
11848                   $component2->setProperty( $propName, $datestring );
11849                 } // end if( $dtendExist || $dueExist || $durationExist )
11850                 $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
11851                 $wd = getdate( $rstart );
11852                 $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
11853               } // end if( !isset( $exdatelist[$checkDate] ))
11854             } // end elseif( $rstart >= $startDate )
11855           } // end foreach( $recurlist as $recurkey => $durvalue )
11856           unset( $component2 );
11857         } // end if( 0 < count( $recurlist ))
11858             /* deselect components with startdate/enddate not within period */
11859         if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
11860           continue;
11861       } // end if( TRUE === $any )
11862     } // end foreach ( $this->components as $cix => $component )
11863     unset( $dtendExist, $dueExist, $durationExist, $endAllDayEvent, $recurrid, $recurridList,
+11864            $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $recurlist, $workstart, $workend, $endDateFormat ); // clean up
11865     if( 0 >= count( $result )) return FALSE;
11866     elseif( !$flat ) {
11867       foreach( $result as $y => $yeararr ) {
11868         foreach( $yeararr as $m => $montharr ) {
11869           foreach( $montharr as $d => $dayarr ) {
11870             if( empty( $result[$y][$m][$d] ))
11871                 unset( $result[$y][$m][$d] );
11872             else
11873               $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
11874           }
11875           if( empty( $result[$y][$m] ))
11876               unset( $result[$y][$m] );
11877           else
11878             ksort( $result[$y][$m] );
11879         }
11880         if( empty( $result[$y] ))
11881             unset( $result[$y] );
11882         else
11883           ksort( $result[$y] );
11884       }
11885       if( empty( $result ))
11886           unset( $result );
11887       else
11888         ksort( $result );
11889     } // end elseif( !$flat )
11890     if( 0 >= count( $result ))
11891       return FALSE;
11892     return $result;
11893   }
11894 /**
11895  * select components from calendar on based on specific property value(-s)
11896  *
11897  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11898  * @since 2.16.6 - 2012-12-26
11899  * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
11900  * @return array
11901  */
11902   function selectComponents2( $selectOptions ) {
11903     $output = array();
11904     $allowedComps      = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
11905     $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
11906     foreach( $this->components as $cix => $component3 ) {
11907       if( !in_array( $component3->objName, $allowedComps ))
11908         continue;
11909       $uid = $component3->getProperty( 'UID' );
11910       foreach( $selectOptions as $propName => $pvalue ) {
11911         $propName = strtoupper( $propName );
11912         if( !in_array( $propName, $allowedProperties ))
11913           continue;
11914         if( !is_array( $pvalue ))
11915           $pvalue = array( $pvalue );
11916         if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
11917           $output[$uid][] = $component3->copy();
11918           continue;
11919         }
11920         elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'CONTACT' == $propName ) || ( 'RELATED-TO' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple occurrence?
11921           $propValues = array();
11922           $component3->_getProperties( $propName, $propValues );
11923           $propValues = array_keys( $propValues );
11924           foreach( $pvalue as $theValue ) {
11925             if( in_array( $theValue, $propValues )) { //  && !isset( $output[$uid] )) {
11926               $output[$uid][] = $component3->copy();
11927               break;
11928             }
11929           }
11930           continue;
11931         } // end   elseif( // multiple occurrence?
11932         elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence
11933           continue;
11934         if( is_array( $d )) {
11935           foreach( $d as $part ) {
11936             if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
11937               $output[$uid][] = $component3->copy();
11938           }
11939         }
11940         elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
11941           foreach( $pvalue as $pval ) {
11942             if( FALSE !== stripos( $d, $pval )) {
11943               $output[$uid][] = $component3->copy();
11944               break;
11945             }
11946           }
11947         }
11948         elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
11949           $output[$uid][] = $component3->copy();
11950       } // end foreach( $selectOptions as $propName => $pvalue ) {
11951     } // end foreach( $this->components as $cix => $component3 ) {
11952     if( !empty( $output )) {
11953       ksort( $output ); // uid order
11954       $output2 = array();
11955       foreach( $output as $uid => $components ) {
11956         foreach( $components as $component )
11957           $output2[] = $component;
11958       }
11959       $output = $output2;
11960     }
11961     return $output;
11962   }
11963 /**
11964  * add calendar component to container
11965  *
11966  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
11967  * @since 2.8.8 - 2011-03-15
11968  * @param object $component calendar component
11969  * @param mixed $arg1 optional, ordno/component type/ component uid
11970  * @param mixed $arg2 optional, ordno if arg1 = component type
11971  * @return void
11972  */
11973   function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
11974     $component->setConfig( $this->getConfig(), FALSE, TRUE );
11975     if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
11976             /* make sure dtstamp and uid is set */
11977       $dummy1 = $component->getProperty( 'dtstamp' );
11978       $dummy2 = $component->getProperty( 'uid' );
11979     }
11980     if( !$arg1 ) { // plain insert, last in chain
11981       $this->components[] = $component->copy();
11982       return TRUE;
11983     }
11984     $argType = $index = null;
11985     if ( ctype_digit( (string) $arg1 )) { // index insert/replace
11986       $argType = 'INDEX';
11987       $index   = (int) $arg1 - 1;
11988     }
11989     elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
11990       $argType = strtolower( $arg1 );
11991       $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
11992     }
11993     // else if arg1 is set, arg1 must be an UID
11994     $cix1sC = 0;
11995     foreach ( $this->components as $cix => $component2) {
11996       if( empty( $component2 )) continue;
11997       if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
11998         $this->components[$cix] = $component->copy();
11999         return TRUE;
12000       }
12001       elseif( $argType == $component2->objName ) { // component Type index insert/replace
12002         if( $index == $cix1sC ) {
12003           $this->components[$cix] = $component->copy();
12004           return TRUE;
12005         }
12006         $cix1sC++;
12007       }
12008       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
12009         $this->components[$cix] = $component->copy();
12010         return TRUE;
12011       }
12012     }
12013             /* arg1=index and not found.. . insert at index .. .*/
12014     if( 'INDEX' == $argType ) {
12015       $this->components[$index] = $component->copy();
12016       ksort( $this->components, SORT_NUMERIC );
12017     }
12018     else    /* not found.. . insert last in chain anyway .. .*/
12019       $this->components[] = $component->copy();
12020     return TRUE;
12021   }
12022 /**
12023  * sort iCal compoments
12024  *
12025  * ascending sort on properties (if exist) x-current-dtstart, dtstart,
12026  * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
12027  * otherwise sorting on specific (argument) property values
12028  *
12029  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12030  * @since 2.16.4 - 2012-12-17
12031  * @param string $sortArg, optional
12032  * @return void
12033  *
12034  */
12035   function sort( $sortArg=FALSE ) {
12036     if( is_array( $this->components )) {
12037       if( $sortArg ) {
12038         $sortArg = strtoupper( $sortArg );
12039         if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' )))
12040           $sortArg = FALSE;
12041       }
12042             /* set sort parameters for each component */
12043       foreach( $this->components as $cix => & $c ) {
12044         $c->srtk = array( '0', '0', '0', '0' );
12045         if( 'vtimezone' == $c->objName ) {
12046           if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
12047             $c->srtk[0] = 0;
12048           continue;
12049         }
12050         elseif( $sortArg ) {
12051           if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'CONTACT' == $sortArg ) || ( 'RELATED-TO' == $sortArg ) || ( 'RESOURCES' == $sortArg )) {
12052             $propValues = array();
12053             $c->_getProperties( $sortArg, $propValues );
12054             if( !empty( $propValues )) {
12055               $sk         = array_keys( $propValues );
12056               $c->srtk[0] = $sk[0];
12057               if( 'RELATED-TO'  == $sortArg )
12058                 $c->srtk[0] .= $c->getProperty( 'uid' );
12059             }
12060             elseif( 'RELATED-TO'  == $sortArg )
12061               $c->srtk[0] = $c->getProperty( 'uid' );
12062           }
12063           elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) {
12064             $c->srtk[0] = $d;
12065             if( 'UID' == $sortArg ) {
12066               if( FALSE !== ( $d = $c->getProperty( 'recurrence-id' ))) {
12067                 $c->srtk[1] = iCalUtilityFunctions::_date2strdate( $d );
12068                 if( FALSE === ( $c->srtk[2] = $c->getProperty( 'sequence' )))
12069                   $c->srtk[2] = PHP_INT_MAX;
12070               }
12071               else
12072                 $c->srtk[1] = $c->srtk[2] = PHP_INT_MAX;
12073             }
12074           }
12075           continue;
12076         }
12077         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
12078           $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] );
12079           unset( $c->srtk[0]['unparsedtext'] );
12080         }
12081         elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
12082           $c->srtk[1] = 0;                                                  // sortkey 0 : dtstart
12083         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
12084           $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration)
12085           unset( $c->srtk[1]['unparsedtext'] );
12086         }
12087         elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
12088           if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
12089             $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );
12090             unset( $c->srtk[1]['unparsedtext'] );
12091           }
12092           elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
12093             if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
12094               $c->srtk[1] = 0;
12095         }
12096         if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
12097           if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
12098             $c->srtk[2] = 0;
12099         if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
12100           $c->srtk[3] = 0;
12101       } // end foreach( $this->components as & $c
12102             /* sort */
12103       usort( $this->components, array( 'iCalUtilityFunctions', '_cmpfcn' ));
12104     }
12105   }
12106 /**
12107  * parse iCal text/file into vcalendar, components, properties and parameters
12108  *
12109  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12110  * @since 2.16.2 - 2012-12-18
12111  * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
12112  * @return bool FALSE if error occurs during parsing
12113  *
12114  */
12115   function parse( $unparsedtext=FALSE ) {
12116     $nl = $this->getConfig( 'nl' );
12117     if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
12118             /* directory+filename is set previously via setConfig directory+filename or url */
12119       if( FALSE === ( $filename = $this->getConfig( 'url' )))
12120         $filename = $this->getConfig( 'dirfile' );
12121             /* READ FILE */
12122       if( FALSE === ( $rows = file_get_contents( $filename )))
12123         return FALSE;                 /* err 1 */
12124     }
12125     elseif( is_array( $unparsedtext ))
12126       $rows =  implode( '\n'.$nl, $unparsedtext );
12127     else
12128       $rows = & $unparsedtext;
12129             /* fix line folding */
12130     $rows = explode( $nl, iCalUtilityFunctions::convEolChar( $rows, $nl ));
12131             /* skip leading (empty/invalid) lines */
12132     foreach( $rows as $lix => $line ) {
12133       if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' ))
12134         break;
12135       unset( $rows[$lix] );
12136     }
12137     $rcnt = count( $rows );
12138     if( 3 > $rcnt )                  /* err 10 */
12139       return FALSE;
12140             /* skip trailing empty lines and ensure an end row */
12141     $lix  = array_keys( $rows );
12142     $lix  = end( $lix );
12143     while( 3 < $lix ) {
12144       $tst = trim( $rows[$lix] );
12145       if(( '\n' == $tst ) || empty( $tst )) {
12146         unset( $rows[$lix] );
12147         $lix--;
12148         continue;
12149       }
12150       if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' ))
12151         $rows[] = 'END:VCALENDAR';
12152       break;
12153     }
12154     $comp    = & $this;
12155     $calsync = $compsync = 0;
12156             /* identify components and update unparsed data within component */
12157     $config = $this->getConfig();
12158     $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' );
12159     foreach( $rows as $lix => $line ) {
12160       if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
12161         $calsync++;
12162         continue;
12163       }
12164       elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
12165         if( 0 < $compsync )
12166           $this->components[] = $comp->copy();
12167         $compsync--;
12168         $calsync--;
12169         break;
12170       }
12171       elseif( 1 != $calsync )
12172         return FALSE;                 /* err 20 */
12173       elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) {
12174         $this->components[] = $comp->copy();
12175         $compsync--;
12176         continue;
12177       }
12178       if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) {
12179         $comp = new vevent( $config );
12180         $compsync++;
12181       }
12182       elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) {
12183         $comp = new vfreebusy( $config );
12184         $compsync++;
12185       }
12186       elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) {
12187         $comp = new vjournal( $config );
12188         $compsync++;
12189       }
12190       elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) {
12191         $comp = new vtodo( $config );
12192         $compsync++;
12193       }
12194       elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) {
12195         $comp = new vtimezone( $config );
12196         $compsync++;
12197       }
12198       else { /* update component with unparsed data */
12199         $comp->unparsed[] = $line;
12200       }
12201     } // end foreach( $rows as $line )
12202     unset( $config, $endtxt );
12203             /* parse data for calendar (this) object */
12204     if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
12205             /* concatenate property values spread over several lines */
12206       $propnames = array( 'calscale','method','prodid','version','x-' );
12207       $proprows  = array();
12208       for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
12209         $line = rtrim( $this->unparsed[$i], $nl );
12210         while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
12211           $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
12212         $proprows[] = $line;
12213       }
12214       $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
12215       $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
12216       $paramProto4 = array( 'crid:', 'news:', 'pres:' );
12217       foreach( $proprows as $line ) {
12218         if( '\n' == substr( $line, -2 ))
12219           $line = substr( $line, 0, -2 );
12220             /* get property name */
12221         $propname  = '';
12222         $cix       = 0;
12223         while( FALSE !== ( $char = substr( $line, $cix, 1 ))) {
12224           if( in_array( $char, array( ':', ';' )))
12225             break;
12226           else
12227             $propname .= $char;
12228           $cix++;
12229         }
12230             /* skip non standard property names */
12231         if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames ))
12232           continue;
12233             /* ignore version/prodid properties */
12234         if( in_array( strtolower( $propname ), array( 'version', 'prodid' )))
12235           continue;
12236             /* rest of the line is opt.params and value */
12237         $line = substr( $line, $cix);
12238             /* separate attributes from value */
12239         $attr         = array();
12240         $attrix       = -1;
12241         $strlen       = strlen( $line );
12242         $WithinQuotes = FALSE;
12243         $cix          = 0;
12244         while( FALSE !== substr( $line, $cix, 1 )) {
12245           if(                       ( ':'  == $line[$cix] )                         &&
+12246                                     ( substr( $line,$cix,     3 )  != '://' )       &&
+12247              ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
+12248              ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
+12249              ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
+12250                         ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
12251                !$WithinQuotes ) {
12252             $attrEnd = TRUE;
12253             if(( $cix < ( $strlen - 4 )) &&
+12254                  ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
12255               for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
12256                 if( '://' == substr( $line, $c2ix - 2, 3 )) {
12257                   $attrEnd = FALSE;
12258                   break; // an URI with a portnr!!
12259                 }
12260               }
12261             }
12262             if( $attrEnd) {
12263               $line = substr( $line, ( $cix + 1 ));
12264               break;
12265             }
12266           }
12267           if( '"' == $line[$cix] )
12268             $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
12269           if( ';' == $line[$cix] )
12270             $attr[++$attrix] = null;
12271           else
12272             $attr[$attrix] .= $line[$cix];
12273           $cix++;
12274         }
12275             /* make attributes in array format */
12276         $propattr = array();
12277         foreach( $attr as $attribute ) {
12278           $attrsplit = explode( '=', $attribute, 2 );
12279           if( 1 < count( $attrsplit ))
12280             $propattr[$attrsplit[0]] = $attrsplit[1];
12281           else
12282             $propattr[] = $attribute;
12283         }
12284             /* update Property */
12285         if( FALSE !== strpos( $line, ',' )) {
12286           $content  = array( 0 => '' );
12287           $cix = $lix = 0;
12288           while( FALSE !== substr( $line, $lix, 1 )) {
12289             if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
12290               $cix++;
12291               $content[$cix] = '';
12292             }
12293             else
12294               $content[$cix] .= $line[$lix];
12295             $lix++;
12296           }
12297           if( 1 < count( $content )) {
12298             foreach( $content as $cix => $contentPart )
12299               $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
12300             $this->setProperty( $propname, $content, $propattr );
12301             continue;
12302           }
12303           else
12304             $line = reset( $content );
12305           $line = iCalUtilityFunctions::_strunrep( $line );
12306         }
12307         $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr );
12308       } // end - foreach( $this->unparsed.. .
12309     } // end - if( is_array( $this->unparsed.. .
12310     unset( $unparsedtext, $rows, $this->unparsed, $proprows );
12311             /* parse Components */
12312     if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
12313       $ckeys = array_keys( $this->components );
12314       foreach( $ckeys as $ckey ) {
12315         if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
12316           $this->components[$ckey]->parse();
12317         }
12318       }
12319     }
12320     else
12321       return FALSE;                   /* err 91 or something.. . */
12322     return TRUE;
12323   }
12324 /*********************************************************************************/
12325 /**
12326  * creates formatted output for calendar object instance
12327  *
12328  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12329  * @since 2.10.16 - 2011-10-28
12330  * @return string
12331  */
12332   function createCalendar() {
12333     $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
12334     switch( $this->format ) {
12335       case 'xcal':
12336         $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
+12337                          '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
+12338                          '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
12339         $calendarStart = '>'.$this->nl.'<vcalendar';
12340         break;
12341       default:
12342         $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
12343         break;
12344     }
12345     $calendarStart .= $this->createVersion();
12346     $calendarStart .= $this->createProdid();
12347     $calendarStart .= $this->createCalscale();
12348     $calendarStart .= $this->createMethod();
12349     if( 'xcal' == $this->format )
12350       $calendarStart .= '>'.$this->nl;
12351     $calendar .= $this->createXprop();
12352 
12353     foreach( $this->components as $component ) {
12354       if( empty( $component )) continue;
12355       $component->setConfig( $this->getConfig(), FALSE, TRUE );
12356       $calendar .= $component->createComponent( $this->xcaldecl );
12357     }
12358     if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
12359       $calendarInit .= ' [';
12360       $old_xcaldecl  = array();
12361       foreach( $this->xcaldecl as $declix => $declPart ) {
12362         if(( 0 < count( $old_xcaldecl))    &&
12363              isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
12364              isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
+12365            ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
+12366            ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
12367           continue; // no duplicate uri and ext. references
12368         if(( 0 < count( $old_xcaldecl))    &&
12369             !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
12370              isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
+12371            ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
12372           continue; // no duplicate element declarations
12373         $calendarxCaldecl .= $this->nl.'<!';
12374         foreach( $declPart as $declKey => $declValue ) {
12375           switch( $declKey ) {                    // index
12376             case 'xmldecl':                       // no 1
12377               $calendarxCaldecl .= $declValue.' ';
12378               break;
12379             case 'uri':                           // no 2
12380               $calendarxCaldecl .= $declValue.' ';
12381               $old_xcaldecl['uri'][] = $declValue;
12382               break;
12383             case 'ref':                           // no 3
12384               $calendarxCaldecl .= $declValue.' ';
12385               $old_xcaldecl['ref'][] = $declValue;
12386               break;
12387             case 'external':                      // no 4
12388               $calendarxCaldecl .= '"'.$declValue.'" ';
12389               $old_xcaldecl['external'][] = $declValue;
12390               break;
12391             case 'type':                          // no 5
12392               $calendarxCaldecl .= $declValue.' ';
12393               break;
12394             case 'type2':                         // no 6
12395               $calendarxCaldecl .= $declValue;
12396               break;
12397           }
12398         }
12399         $calendarxCaldecl .= '>';
12400       }
12401       $calendarxCaldecl .= $this->nl.']';
12402     }
12403     switch( $this->format ) {
12404       case 'xcal':
12405         $calendar .= '</vcalendar>'.$this->nl;
12406         break;
12407       default:
12408         $calendar .= 'END:VCALENDAR'.$this->nl;
12409         break;
12410     }
12411     return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
12412   }
12413 /**
12414  * a HTTP redirect header is sent with created, updated and/or parsed calendar
12415  *
12416  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12417  * @since 2.10.24 - 2011-12-23
12418  * @param bool $utf8Encode
12419  * @param bool $gzip
12420  * @return redirect
12421  */
12422   function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
12423     $filename = $this->getConfig( 'filename' );
12424     $output   = $this->createCalendar();
12425     if( $utf8Encode )
12426       $output = utf8_encode( $output );
12427     if( $gzip ) {
12428       $output = gzencode( $output, 9 );
12429       header( 'Content-Encoding: gzip' );
12430       header( 'Vary: *' );
12431       header( 'Content-Length: '.strlen( $output ));
12432     }
12433     if( 'xcal' == $this->format )
12434       header( 'Content-Type: application/calendar+xml; charset=utf-8' );
12435     else
12436       header( 'Content-Type: text/calendar; charset=utf-8' );
12437     header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
12438     header( 'Cache-Control: max-age=10' );
12439     die( $output );
12440   }
12441 /**
12442  * save content in a file
12443  *
12444  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12445  * @since 2.2.12 - 2007-12-30
12446  * @param string $directory optional
12447  * @param string $filename optional
12448  * @param string $delimiter optional
12449  * @return bool
12450  */
12451   function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
12452     if( $directory )
12453       $this->setConfig( 'directory', $directory );
12454     if( $filename )
12455       $this->setConfig( 'filename',  $filename );
12456     if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
12457       $this->setConfig( 'delimiter', $delimiter );
12458     if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
12459       $dirfile = $this->getConfig( 'dirfile' );
12460     $iCalFile = @fopen( $dirfile, 'w' );
12461     if( $iCalFile ) {
12462       if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
12463         return FALSE;
12464       fclose( $iCalFile );
12465       return TRUE;
12466     }
12467     else
12468       return FALSE;
12469   }
12470 /**
12471  * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
12472  * else FALSE is returned
12473  *
12474  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12475  * @since 2.2.12 - 2007-10-28
12476  * @param string $directory optional alt. int timeout
12477  * @param string $filename optional
12478  * @param string $delimiter optional
12479  * @param int timeout optional, default 3600 sec
12480  * @return redirect/FALSE
12481  */
12482   function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
12483     if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
12484       $timeout   = (int) $directory;
12485       $directory = FALSE;
12486     }
12487     if( $directory )
12488       $this->setConfig( 'directory', $directory );
12489     if( $filename )
12490       $this->setConfig( 'filename',  $filename );
12491     if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
12492       $this->setConfig( 'delimiter', $delimiter );
12493     $filesize    = $this->getConfig( 'filesize' );
12494     if( 0 >= $filesize )
12495       return FALSE;
12496     $dirfile     = $this->getConfig( 'dirfile' );
12497     if( time() - filemtime( $dirfile ) < $timeout) {
12498       clearstatcache();
12499       $dirfile   = $this->getConfig( 'dirfile' );
12500       $filename  = $this->getConfig( 'filename' );
12501 //    if( headers_sent( $filename, $linenum ))
12502 //      die( "Headers already sent in $filename on line $linenum\n" );
12503       if( 'xcal' == $this->format )
12504         header( 'Content-Type: application/calendar+xml; charset=utf-8' );
12505       else
12506         header( 'Content-Type: text/calendar; charset=utf-8' );
12507       header( 'Content-Length: '.$filesize );
12508       header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
12509       header( 'Cache-Control: max-age=10' );
12510       $fp = @fopen( $dirfile, 'r' );
12511       if( $fp ) {
12512         fpassthru( $fp );
12513         fclose( $fp );
12514       }
12515       die();
12516     }
12517     else
12518       return FALSE;
12519   }
12520 }
12521 /*********************************************************************************/
12522 /*********************************************************************************/
12523 /**
12524  *  abstract class for calendar components
12525  *
12526  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12527  * @since 2.9.6 - 2011-05-14
12528  */
12529 class calendarComponent {
12530             //  component property variables
12531   var $uid;
12532   var $dtstamp;
12533 
12534             //  component config variables
12535   var $allowEmpty;
12536   var $language;
12537   var $nl;
12538   var $unique_id;
12539   var $format;
12540   var $objName; // created automatically at instance creation
12541   var $dtzid;   // default (local) timezone
12542             //  component internal variables
12543   var $componentStart1;
12544   var $componentStart2;
12545   var $componentEnd1;
12546   var $componentEnd2;
12547   var $elementStart1;
12548   var $elementStart2;
12549   var $elementEnd1;
12550   var $elementEnd2;
12551   var $intAttrDelimiter;
12552   var $attributeDelimiter;
12553   var $valueInit;
12554             //  component xCal declaration container
12555   var $xcaldecl;
12556 /**
12557  * constructor for calendar component object
12558  *
12559  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12560  * @since 2.9.6 - 2011-05-17
12561  */
12562   function calendarComponent() {
12563     $this->objName         = ( isset( $this->timezonetype )) ?
12564                           strtolower( $this->timezonetype )  :  get_class ( $this );
12565     $this->uid             = array();
12566     $this->dtstamp         = array();
12567 
12568     $this->language        = null;
12569     $this->nl              = null;
12570     $this->unique_id       = null;
12571     $this->format          = null;
12572     $this->dtzid           = null;
12573     $this->allowEmpty      = TRUE;
12574     $this->xcaldecl        = array();
12575 
12576     $this->_createFormat();
12577     $this->_makeDtstamp();
12578   }
12579 /*********************************************************************************/
12580 /**
12581  * Property Name: ACTION
12582  */
12583 /**
12584  * creates formatted output for calendar component property action
12585  *
12586  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12587  * @since 2.4.8 - 2008-10-22
12588  * @return string
12589  */
12590   function createAction() {
12591     if( empty( $this->action )) return FALSE;
12592     if( empty( $this->action['value'] ))
12593       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
12594     $attributes = $this->_createParams( $this->action['params'] );
12595     return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
12596   }
12597 /**
12598  * set calendar component property action
12599  *
12600  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12601  * @since 2.4.8 - 2008-11-04
12602  * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
12603  * @param mixed $params
12604  * @return bool
12605  */
12606   function setAction( $value, $params=FALSE ) {
12607     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
12608     $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
12609     return TRUE;
12610   }
12611 /*********************************************************************************/
12612 /**
12613  * Property Name: ATTACH
12614  */
12615 /**
12616  * creates formatted output for calendar component property attach
12617  *
12618  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12619  * @since 2.11.16 - 2012-02-04
12620  * @return string
12621  */
12622   function createAttach() {
12623     if( empty( $this->attach )) return FALSE;
12624     $output       = null;
12625     foreach( $this->attach as $attachPart ) {
12626       if( !empty( $attachPart['value'] )) {
12627         $attributes = $this->_createParams( $attachPart['params'] );
12628         if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
12629           $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
12630           $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
12631           $output     = substr( $str, 0, 75 ).$this->nl;
12632           $str        = substr( $str, 75 );
12633           $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' );
12634           if( ' ' == substr( $output, -1 ))
12635             $output   = rtrim( $output );
12636           if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
12637             $output  .= $this->nl;
12638           return $output;
12639         }
12640         $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
12641       }
12642       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
12643     }
12644     return $output;
12645   }
12646 /**
12647  * set calendar component property attach
12648  *
12649  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12650  * @since 2.5.1 - 2008-11-06
12651  * @param string $value
12652  * @param array $params, optional
12653  * @param integer $index, optional
12654  * @return bool
12655  */
12656   function setAttach( $value, $params=FALSE, $index=FALSE ) {
12657     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
12658     iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
12659     return TRUE;
12660   }
12661 /*********************************************************************************/
12662 /**
12663  * Property Name: ATTENDEE
12664  */
12665 /**
12666  * creates formatted output for calendar component property attendee
12667  *
12668  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12669  * @since 2.11.12 - 2012-01-31
12670  * @return string
12671  */
12672   function createAttendee() {
12673     if( empty( $this->attendee )) return FALSE;
12674     $output = null;
12675     foreach( $this->attendee as $attendeePart ) {                      // start foreach 1
12676       if( empty( $attendeePart['value'] )) {
12677         if( $this->getConfig( 'allowEmpty' ))
12678           $output .= $this->_createElement( 'ATTENDEE' );
12679         continue;
12680       }
12681       $attendee1 = $attendee2 = null;
12682       foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
12683         if( 'value' == $paramlabel )
12684           $attendee2     .= $paramvalue;
12685         elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
12686           $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
12687           foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes
12688             if( is_array( $pValue ) || in_array( $pKey, $mParams ))
12689               continue;
12690             if(( FALSE !== strpos( $pValue, ':' )) ||
+12691                ( FALSE !== strpos( $pValue, ';' )) ||
+12692                ( FALSE !== strpos( $pValue, ',' )))
12693               $paramvalue[$pKey] = '"'.$pValue.'"';
12694           }
12695         // set attenddee parameters in rfc2445 order
12696           if( isset( $paramvalue['CUTYPE'] ))
12697             $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
12698           if( isset( $paramvalue['MEMBER'] )) {
12699             $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
12700             foreach( $paramvalue['MEMBER'] as $cix => $opv )
12701               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
12702           }
12703           if( isset( $paramvalue['ROLE'] ))
12704             $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
12705           if( isset( $paramvalue['PARTSTAT'] ))
12706             $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
12707           if( isset( $paramvalue['RSVP'] ))
12708             $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
12709           if( isset( $paramvalue['DELEGATED-TO'] )) {
12710             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
12711             foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
12712               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
12713           }
12714           if( isset( $paramvalue['DELEGATED-FROM'] )) {
12715             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
12716             foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
12717               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
12718           }
12719           if( isset( $paramvalue['SENT-BY'] ))
12720             $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
12721           if( isset( $paramvalue['CN'] ))
12722             $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
12723           if( isset( $paramvalue['DIR'] )) {
12724             $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
12725             $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
12726           }
12727           if( isset( $paramvalue['LANGUAGE'] ))
12728             $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
12729           $xparams = array();
12730           foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
12731             if( ctype_digit( (string) $optparamlabel )) {
12732               $xparams[]  = $optparamvalue;
12733               continue;
12734             }
12735             if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
12736               $xparams[$optparamlabel] = $optparamvalue;
12737           } // end foreach 3
12738           ksort( $xparams, SORT_STRING );
12739           foreach( $xparams as $paramKey => $paramValue ) {
12740             if( ctype_digit( (string) $paramKey ))
12741               $attendee1 .= $this->intAttrDelimiter.$paramValue;
12742             else
12743               $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
12744           }      // end foreach 3
12745         }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
12746       }          // end foreach 2
12747       $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
12748     }              // end foreach 1
12749     return $output;
12750   }
12751 /**
12752  * set calendar component property attach
12753  *
12754  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12755  * @since 2.12.18 - 2012-07-13
12756  * @param string $value
12757  * @param array $params, optional
12758  * @param integer $index, optional
12759  * @return bool
12760  */
12761   function setAttendee( $value, $params=FALSE, $index=FALSE ) {
12762     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
12763           // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
12764     if( !empty( $value )) {
12765       if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
12766         $value = 'MAILTO:'.$value;
12767       elseif( !empty( $value ))
12768         $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
12769       $value = str_replace( 'mailto:', 'MAILTO:', $value );
12770     }
12771     $params2 = array();
12772     if( is_array($params )) {
12773       $optarrays = array();
12774       foreach( $params as $optparamlabel => $optparamvalue ) {
12775         $optparamlabel = strtoupper( $optparamlabel );
12776         switch( $optparamlabel ) {
12777           case 'MEMBER':
12778           case 'DELEGATED-TO':
12779           case 'DELEGATED-FROM':
12780             if( !is_array( $optparamvalue ))
12781               $optparamvalue = array( $optparamvalue );
12782             foreach( $optparamvalue as $part ) {
12783               $part = trim( $part );
12784               if(( '"' == substr( $part, 0, 1 )) &&
+12785                  ( '"' == substr( $part, -1 )))
12786                 $part = substr( $part, 1, ( strlen( $part ) - 2 ));
12787               if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
12788                 $part = "MAILTO:$part";
12789               else
12790                 $part = 'MAILTO:'.substr( $part, 7 );
12791               $optarrays[$optparamlabel][] = $part;
12792             }
12793             break;
12794           default:
12795             if(( '"' == substr( $optparamvalue, 0, 1 )) &&
+12796                ( '"' == substr( $optparamvalue, -1 )))
12797               $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
12798             if( 'SENT-BY' ==  $optparamlabel ) {
12799               if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
12800                 $optparamvalue = "MAILTO:$optparamvalue";
12801               else
12802                 $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
12803             }
12804             $params2[$optparamlabel] = $optparamvalue;
12805             break;
12806         } // end switch( $optparamlabel.. .
12807       } // end foreach( $optparam.. .
12808       foreach( $optarrays as $optparamlabel => $optparams )
12809         $params2[$optparamlabel] = $optparams;
12810     }
12811         // remove defaults
12812     iCalUtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
12813     iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
12814     iCalUtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
12815     iCalUtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
12816         // check language setting
12817     if( isset( $params2['CN' ] )) {
12818       $lang = $this->getConfig( 'language' );
12819       if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
12820         $params2['LANGUAGE' ] = $lang;
12821     }
12822     iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
12823     return TRUE;
12824   }
12825 /*********************************************************************************/
12826 /**
12827  * Property Name: CATEGORIES
12828  */
12829 /**
12830  * creates formatted output for calendar component property categories
12831  *
12832  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12833  * @since 2.16.2 - 2012-12-18
12834  * @return string
12835  */
12836   function createCategories() {
12837     if( empty( $this->categories )) return FALSE;
12838     $output = null;
12839     foreach( $this->categories as $category ) {
12840       if( empty( $category['value'] )) {
12841         if ( $this->getConfig( 'allowEmpty' ))
12842           $output .= $this->_createElement( 'CATEGORIES' );
12843         continue;
12844       }
12845       $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
12846       if( is_array( $category['value'] )) {
12847         foreach( $category['value'] as $cix => $categoryPart )
12848           $category['value'][$cix] = iCalUtilityFunctions::_strrep( $categoryPart, $this->format, $this->nl );
12849         $content  = implode( ',', $category['value'] );
12850       }
12851       else
12852         $content  = iCalUtilityFunctions::_strrep( $category['value'], $this->format, $this->nl );
12853       $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
12854     }
12855     return $output;
12856   }
12857 /**
12858  * set calendar component property categories
12859  *
12860  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12861  * @since 2.5.1 - 2008-11-06
12862  * @param mixed $value
12863  * @param array $params, optional
12864  * @param integer $index, optional
12865  * @return bool
12866  */
12867   function setCategories( $value, $params=FALSE, $index=FALSE ) {
12868     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
12869     iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
12870     return TRUE;
12871  }
12872 /*********************************************************************************/
12873 /**
12874  * Property Name: CLASS
12875  */
12876 /**
12877  * creates formatted output for calendar component property class
12878  *
12879  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12880  * @since 0.9.7 - 2006-11-20
12881  * @return string
12882  */
12883   function createClass() {
12884     if( empty( $this->class )) return FALSE;
12885     if( empty( $this->class['value'] ))
12886       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
12887     $attributes = $this->_createParams( $this->class['params'] );
12888     return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
12889   }
12890 /**
12891  * set calendar component property class
12892  *
12893  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12894  * @since 2.4.8 - 2008-11-04
12895  * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
12896  * @param array $params optional
12897  * @return bool
12898  */
12899   function setClass( $value, $params=FALSE ) {
12900     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
12901     $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
12902     return TRUE;
12903   }
12904 /*********************************************************************************/
12905 /**
12906  * Property Name: COMMENT
12907  */
12908 /**
12909  * creates formatted output for calendar component property comment
12910  *
12911  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12912  * @since 2.16.2 - 2012-12-18
12913  * @return string
12914  */
12915   function createComment() {
12916     if( empty( $this->comment )) return FALSE;
12917     $output = null;
12918     foreach( $this->comment as $commentPart ) {
12919       if( empty( $commentPart['value'] )) {
12920         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
12921         continue;
12922       }
12923       $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
12924       $content    = iCalUtilityFunctions::_strrep( $commentPart['value'], $this->format, $this->nl );
12925       $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
12926     }
12927     return $output;
12928   }
12929 /**
12930  * set calendar component property comment
12931  *
12932  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12933  * @since 2.5.1 - 2008-11-06
12934  * @param string $value
12935  * @param array $params, optional
12936  * @param integer $index, optional
12937  * @return bool
12938  */
12939   function setComment( $value, $params=FALSE, $index=FALSE ) {
12940     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
12941     iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
12942     return TRUE;
12943   }
12944 /*********************************************************************************/
12945 /**
12946  * Property Name: COMPLETED
12947  */
12948 /**
12949  * creates formatted output for calendar component property completed
12950  *
12951  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12952  * @since 2.4.8 - 2008-10-22
12953  * @return string
12954  */
12955   function createCompleted( ) {
12956     if( empty( $this->completed )) return FALSE;
12957     if( !isset( $this->completed['value']['year'] )  &&
12958         !isset( $this->completed['value']['month'] ) &&
12959         !isset( $this->completed['value']['day'] )   &&
12960         !isset( $this->completed['value']['hour'] )  &&
12961         !isset( $this->completed['value']['min'] )   &&
12962         !isset( $this->completed['value']['sec'] ))
12963       if( $this->getConfig( 'allowEmpty' ))
12964         return $this->_createElement( 'COMPLETED' );
12965       else return FALSE;
12966     $formatted  = iCalUtilityFunctions::_date2strdate( $this->completed['value'], 7 );
12967     $attributes = $this->_createParams( $this->completed['params'] );
12968     return $this->_createElement( 'COMPLETED', $attributes, $formatted );
12969   }
12970 /**
12971  * set calendar component property completed
12972  *
12973  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
12974  * @since 2.4.8 - 2008-10-23
12975  * @param mixed $year
12976  * @param mixed $month optional
12977  * @param int $day optional
12978  * @param int $hour optional
12979  * @param int $min optional
12980  * @param int $sec optional
12981  * @param array $params optional
12982  * @return bool
12983  */
12984   function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
12985     if( empty( $year )) {
12986       if( $this->getConfig( 'allowEmpty' )) {
12987         $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
12988         return TRUE;
12989       }
12990       else
12991         return FALSE;
12992     }
12993     $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
12994     return TRUE;
12995   }
12996 /*********************************************************************************/
12997 /**
12998  * Property Name: CONTACT
12999  */
13000 /**
13001  * creates formatted output for calendar component property contact
13002  *
13003  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13004  * @since 2.16.2 - 2012-12-18
13005  * @return string
13006  */
13007   function createContact() {
13008     if( empty( $this->contact )) return FALSE;
13009     $output = null;
13010     foreach( $this->contact as $contact ) {
13011       if( !empty( $contact['value'] )) {
13012         $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
13013         $content    = iCalUtilityFunctions::_strrep( $contact['value'], $this->format, $this->nl );
13014         $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
13015       }
13016       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
13017     }
13018     return $output;
13019   }
13020 /**
13021  * set calendar component property contact
13022  *
13023  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13024  * @since 2.5.1 - 2008-11-05
13025  * @param string $value
13026  * @param array $params, optional
13027  * @param integer $index, optional
13028  * @return bool
13029  */
13030   function setContact( $value, $params=FALSE, $index=FALSE ) {
13031     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
13032     iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
13033     return TRUE;
13034   }
13035 /*********************************************************************************/
13036 /**
13037  * Property Name: CREATED
13038  */
13039 /**
13040  * creates formatted output for calendar component property created
13041  *
13042  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13043  * @since 2.4.8 - 2008-10-21
13044  * @return string
13045  */
13046   function createCreated() {
13047     if( empty( $this->created )) return FALSE;
13048     $formatted  = iCalUtilityFunctions::_date2strdate( $this->created['value'], 7 );
13049     $attributes = $this->_createParams( $this->created['params'] );
13050     return $this->_createElement( 'CREATED', $attributes, $formatted );
13051   }
13052 /**
13053  * set calendar component property created
13054  *
13055  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13056  * @since 2.4.8 - 2008-10-23
13057  * @param mixed $year optional
13058  * @param mixed $month optional
13059  * @param int $day optional
13060  * @param int $hour optional
13061  * @param int $min optional
13062  * @param int $sec optional
13063  * @param mixed $params optional
13064  * @return bool
13065  */
13066   function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
13067     if( !isset( $year )) {
13068       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
13069     }
13070     $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
13071     return TRUE;
13072   }
13073 /*********************************************************************************/
13074 /**
13075  * Property Name: DESCRIPTION
13076  */
13077 /**
13078  * creates formatted output for calendar component property description
13079  *
13080  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13081  * @since 2.16.2 - 2012-12-18
13082  * @return string
13083  */
13084   function createDescription() {
13085     if( empty( $this->description )) return FALSE;
13086     $output       = null;
13087     foreach( $this->description as $description ) {
13088       if( !empty( $description['value'] )) {
13089         $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
13090         $content    = iCalUtilityFunctions::_strrep( $description['value'], $this->format, $this->nl );
13091         $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
13092       }
13093       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
13094     }
13095     return $output;
13096   }
13097 /**
13098  * set calendar component property description
13099  *
13100  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13101  * @since 2.6.24 - 2010-11-06
13102  * @param string $value
13103  * @param array $params, optional
13104  * @param integer $index, optional
13105  * @return bool
13106  */
13107   function setDescription( $value, $params=FALSE, $index=FALSE ) {
13108     if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; }
13109     if( 'vjournal' != $this->objName )
13110       $index = 1;
13111     iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
13112     return TRUE;
13113   }
13114 /*********************************************************************************/
13115 /**
13116  * Property Name: DTEND
13117  */
13118 /**
13119  * creates formatted output for calendar component property dtend
13120  *
13121  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13122  * @since 2.14.4 - 2012-09-26
13123  * @return string
13124  */
13125   function createDtend() {
13126     if( empty( $this->dtend )) return FALSE;
13127     if( !isset( $this->dtend['value']['year'] )  &&
13128         !isset( $this->dtend['value']['month'] ) &&
13129         !isset( $this->dtend['value']['day'] )   &&
13130         !isset( $this->dtend['value']['hour'] )  &&
13131         !isset( $this->dtend['value']['min'] )   &&
13132         !isset( $this->dtend['value']['sec'] ))
13133       if( $this->getConfig( 'allowEmpty' ))
13134         return $this->_createElement( 'DTEND' );
13135       else return FALSE;
13136     $parno      = ( isset( $this->dtend['params']['VALUE'] ) && ( 'DATE' == $this->dtend['params']['VALUE'] )) ? 3 : null;
13137     $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtend['value'], $parno );
13138     $attributes = $this->_createParams( $this->dtend['params'] );
13139     return $this->_createElement( 'DTEND', $attributes, $formatted );
13140   }
13141 /**
13142  * set calendar component property dtend
13143  *
13144  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13145  * @since 2.9.6 - 2011-05-14
13146  * @param mixed $year
13147  * @param mixed $month optional
13148  * @param int $day optional
13149  * @param int $hour optional
13150  * @param int $min optional
13151  * @param int $sec optional
13152  * @param string $tz optional
13153  * @param array params optional
13154  * @return bool
13155  */
13156   function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
13157     if( empty( $year )) {
13158       if( $this->getConfig( 'allowEmpty' )) {
13159         $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
13160         return TRUE;
13161       }
13162       else
13163         return FALSE;
13164     }
13165     $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
13166     return TRUE;
13167   }
13168 /*********************************************************************************/
13169 /**
13170  * Property Name: DTSTAMP
13171  */
13172 /**
13173  * creates formatted output for calendar component property dtstamp
13174  *
13175  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13176  * @since 2.4.4 - 2008-03-07
13177  * @return string
13178  */
13179   function createDtstamp() {
13180     if( !isset( $this->dtstamp['value']['year'] )  &&
13181         !isset( $this->dtstamp['value']['month'] ) &&
13182         !isset( $this->dtstamp['value']['day'] )   &&
13183         !isset( $this->dtstamp['value']['hour'] )  &&
13184         !isset( $this->dtstamp['value']['min'] )   &&
13185         !isset( $this->dtstamp['value']['sec'] ))
13186       $this->_makeDtstamp();
13187     $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstamp['value'], 7 );
13188     $attributes = $this->_createParams( $this->dtstamp['params'] );
13189     return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
13190   }
13191 /**
13192  * computes datestamp for calendar component object instance dtstamp
13193  *
13194  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13195  * @since 2.14.1 - 2012-09-29
13196  * @return void
13197  */
13198   function _makeDtstamp() {
13199     $d    = date( 'Y-m-d-H-i-s', mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')));
13200     $date = explode( '-', $d );
13201     $this->dtstamp['value'] = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2], 'hour' => $date[3], 'min' => $date[4], 'sec' => $date[5], 'tz' => 'Z' );
13202     $this->dtstamp['params'] = null;
13203   }
13204 /**
13205  * set calendar component property dtstamp
13206  *
13207  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13208  * @since 2.4.8 - 2008-10-23
13209  * @param mixed $year
13210  * @param mixed $month optional
13211  * @param int $day optional
13212  * @param int $hour optional
13213  * @param int $min optional
13214  * @param int $sec optional
13215  * @param array $params optional
13216  * @return TRUE
13217  */
13218   function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
13219     if( empty( $year ))
13220       $this->_makeDtstamp();
13221     else
13222       $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
13223     return TRUE;
13224   }
13225 /*********************************************************************************/
13226 /**
13227  * Property Name: DTSTART
13228  */
13229 /**
13230  * creates formatted output for calendar component property dtstart
13231  *
13232  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13233  * @since 2.14.4 - 2012-09-26
13234  * @return string
13235  */
13236   function createDtstart() {
13237     if( empty( $this->dtstart )) return FALSE;
13238     if( !isset( $this->dtstart['value']['year'] )  &&
13239         !isset( $this->dtstart['value']['month'] ) &&
13240         !isset( $this->dtstart['value']['day'] )   &&
13241         !isset( $this->dtstart['value']['hour'] )  &&
13242         !isset( $this->dtstart['value']['min'] )   &&
13243         !isset( $this->dtstart['value']['sec'] )) {
13244       if( $this->getConfig( 'allowEmpty' ))
13245         return $this->_createElement( 'DTSTART' );
13246       else return FALSE;
13247     }
13248     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
13249        unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
13250     $parno      = ( isset( $this->dtstart['params']['VALUE'] ) && ( 'DATE' == $this->dtstart['params']['VALUE'] )) ? 3 : null;
13251     $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstart['value'], $parno );
13252     $attributes = $this->_createParams( $this->dtstart['params'] );
13253     return $this->_createElement( 'DTSTART', $attributes, $formatted );
13254   }
13255 /**
13256  * set calendar component property dtstart
13257  *
13258  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13259  * @since 2.6.22 - 2010-09-22
13260  * @param mixed $year
13261  * @param mixed $month optional
13262  * @param int $day optional
13263  * @param int $hour optional
13264  * @param int $min optional
13265  * @param int $sec optional
13266  * @param string $tz optional
13267  * @param array $params optional
13268  * @return bool
13269  */
13270   function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
13271     if( empty( $year )) {
13272       if( $this->getConfig( 'allowEmpty' )) {
13273         $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
13274         return TRUE;
13275       }
13276       else
13277         return FALSE;
13278     }
13279     $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
13280     return TRUE;
13281   }
13282 /*********************************************************************************/
13283 /**
13284  * Property Name: DUE
13285  */
13286 /**
13287  * creates formatted output for calendar component property due
13288  *
13289  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13290  * @since 2.14.4 - 2012-09-26
13291  * @return string
13292  */
13293   function createDue() {
13294     if( empty( $this->due )) return FALSE;
13295     if( !isset( $this->due['value']['year'] )  &&
13296         !isset( $this->due['value']['month'] ) &&
13297         !isset( $this->due['value']['day'] )   &&
13298         !isset( $this->due['value']['hour'] )  &&
13299         !isset( $this->due['value']['min'] )   &&
13300         !isset( $this->due['value']['sec'] )) {
13301       if( $this->getConfig( 'allowEmpty' ))
13302         return $this->_createElement( 'DUE' );
13303       else
13304        return FALSE;
13305     }
13306     $parno      = ( isset( $this->due['params']['VALUE'] ) && ( 'DATE' == $this->due['params']['VALUE'] )) ? 3 : null;
13307     $formatted  = iCalUtilityFunctions::_date2strdate( $this->due['value'], $parno );
13308     $attributes = $this->_createParams( $this->due['params'] );
13309     return $this->_createElement( 'DUE', $attributes, $formatted );
13310   }
13311 /**
13312  * set calendar component property due
13313  *
13314  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13315  * @since 2.4.8 - 2008-11-04
13316  * @param mixed $year
13317  * @param mixed $month optional
13318  * @param int $day optional
13319  * @param int $hour optional
13320  * @param int $min optional
13321  * @param int $sec optional
13322  * @param array $params optional
13323  * @return bool
13324  */
13325   function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
13326     if( empty( $year )) {
13327       if( $this->getConfig( 'allowEmpty' )) {
13328         $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
13329         return TRUE;
13330       }
13331       else
13332         return FALSE;
13333     }
13334     $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
13335     return TRUE;
13336   }
13337 /*********************************************************************************/
13338 /**
13339  * Property Name: DURATION
13340  */
13341 /**
13342  * creates formatted output for calendar component property duration
13343  *
13344  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13345  * @since 2.4.8 - 2008-10-21
13346  * @return string
13347  */
13348   function createDuration() {
13349     if( empty( $this->duration )) return FALSE;
13350     if( !isset( $this->duration['value']['week'] ) &&
13351         !isset( $this->duration['value']['day'] )  &&
13352         !isset( $this->duration['value']['hour'] ) &&
13353         !isset( $this->duration['value']['min'] )  &&
13354         !isset( $this->duration['value']['sec'] ))
13355       if( $this->getConfig( 'allowEmpty' ))
13356         return $this->_createElement( 'DURATION', array(), null );
13357       else return FALSE;
13358     $attributes = $this->_createParams( $this->duration['params'] );
13359     return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_duration2str( $this->duration['value'] ));
13360   }
13361 /**
13362  * set calendar component property duration
13363  *
13364  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13365  * @since 2.4.8 - 2008-11-04
13366  * @param mixed $week
13367  * @param mixed $day optional
13368  * @param int $hour optional
13369  * @param int $min optional
13370  * @param int $sec optional
13371  * @param array $params optional
13372  * @return bool
13373  */
13374   function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
13375     if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE;
13376     if( is_array( $week ) && ( 1 <= count( $week )))
13377       $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
13378     elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
13379       $week = trim( $week );
13380       if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
13381         $week = substr( $week, 1 );
13382       $this->duration = array( 'value' => iCalUtilityFunctions::_durationStr2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
13383     }
13384     elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec ))
13385       return FALSE;
13386     else
13387       $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params ));
13388     return TRUE;
13389   }
13390 /*********************************************************************************/
13391 /**
13392  * Property Name: EXDATE
13393  */
13394 /**
13395  * creates formatted output for calendar component property exdate
13396  *
13397  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13398  * @since 2.16.5 - 2012-12-28
13399  * @return string
13400  */
13401   function createExdate() {
13402     if( empty( $this->exdate )) return FALSE;
13403     $output  = null;
13404     $exdates = array();
13405     foreach( $this->exdate as $theExdate ) {
13406       if( empty( $theExdate['value'] )) {
13407         if( $this->getConfig( 'allowEmpty' ))
13408           $output .= $this->_createElement( 'EXDATE' );
13409         continue;
13410       }
13411       if( 1 < count( $theExdate['value'] ))
13412         usort( $theExdate['value'], array( 'iCalUtilityFunctions', '_sortExdate1' ));
13413       $exdates[] = $theExdate;
13414     }
13415     if( 1 < count( $exdates ))
13416       usort( $exdates, array( 'iCalUtilityFunctions', '_sortExdate2' ));
13417     foreach( $exdates as $theExdate ) {
13418       $content = $attributes = null;
13419       foreach( $theExdate['value'] as $eix => $exdatePart ) {
13420         $parno = count( $exdatePart );
13421         $formatted = iCalUtilityFunctions::_date2strdate( $exdatePart, $parno );
13422         if( isset( $theExdate['params']['TZID'] ))
13423           $formatted = str_replace( 'Z', '', $formatted);
13424         if( 0 < $eix ) {
13425           if( isset( $theExdate['value'][0]['tz'] )) {
13426             if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
+13427                ( 'Z' == $theExdate['value'][0]['tz'] )) {
13428               if( 'Z' != substr( $formatted, -1 ))
13429                 $formatted .= 'Z';
13430             }
13431             else
13432               $formatted = str_replace( 'Z', '', $formatted );
13433           }
13434           else
13435             $formatted = str_replace( 'Z', '', $formatted );
13436         } // end if( 0 < $eix )
13437         $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
13438       } // end foreach( $theExdate['value'] as $eix => $exdatePart )
13439       $attributes .= $this->_createParams( $theExdate['params'] );
13440       $output .= $this->_createElement( 'EXDATE', $attributes, $content );
13441     } // end foreach( $exdates as $theExdate )
13442     return $output;
13443   }
13444 /**
13445  * set calendar component property exdate
13446  *
13447  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13448  * @since 2.14.1 - 2012-10-02
13449  * @param array exdates
13450  * @param array $params, optional
13451  * @param integer $index, optional
13452  * @return bool
13453  */
13454   function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
13455     if( empty( $exdates )) {
13456       if( $this->getConfig( 'allowEmpty' )) {
13457         iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index );
13458         return TRUE;
13459       }
13460       else
13461         return FALSE;
13462     }
13463     $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
13464     $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
13465             /* ev. check 1:st date and save ev. timezone **/
13466     iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
13467     iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
13468     foreach( $exdates as $eix => $theExdate ) {
13469       iCalUtilityFunctions::_strDate2arr( $theExdate );
13470       if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) {
13471         if( isset( $theExdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theExdate['tz'] )) {
13472           if( isset( $input['params']['TZID'] ))
13473             $theExdate['tz'] = $input['params']['TZID'];
13474           else
13475             $input['params']['TZID'] = $theExdate['tz'];
13476         }
13477         $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
13478       }
13479       elseif(  is_array( $theExdate )) {
13480         $d = iCalUtilityFunctions::_chkDateArr( $theExdate, $parno );
13481         if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
13482           $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
13483           $exdatea = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
13484           unset( $exdatea['unparsedtext'] );
13485         }
13486         else
13487           $exdatea = $d;
13488       }
13489       elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
13490         $exdatea = iCalUtilityFunctions::_strdate2date( $theExdate, $parno );
13491         unset( $exdatea['unparsedtext'] );
13492       }
13493       if( 3 == $parno )
13494         unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
13495       elseif( isset( $exdatea['tz'] ))
13496         $exdatea['tz'] = (string) $exdatea['tz'];
13497       if(  isset( $input['params']['TZID'] ) ||
+13498          ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
+13499          ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
+13500          ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
13501         unset( $exdatea['tz'] );
13502       if( $toZ ) // time zone Z
13503         $exdatea['tz'] = 'Z';
13504       $input['value'][] = $exdatea;
13505     }
13506     if( 0 >= count( $input['value'] ))
13507       return FALSE;
13508     if( 3 == $parno ) {
13509       $input['params']['VALUE'] = 'DATE';
13510       unset( $input['params']['TZID'] );
13511     }
13512     if( $toZ ) // time zone Z
13513       unset( $input['params']['TZID'] );
13514     iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
13515     return TRUE;
13516   }
13517 /*********************************************************************************/
13518 /**
13519  * Property Name: EXRULE
13520  */
13521 /**
13522  * creates formatted output for calendar component property exrule
13523  *
13524  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13525  * @since 2.4.8 - 2008-10-22
13526  * @return string
13527  */
13528   function createExrule() {
13529     if( empty( $this->exrule )) return FALSE;
13530     return $this->_format_recur( 'EXRULE', $this->exrule );
13531   }
13532 /**
13533  * set calendar component property exdate
13534  *
13535  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13536  * @since 2.5.1 - 2008-11-05
13537  * @param array $exruleset
13538  * @param array $params, optional
13539  * @param integer $index, optional
13540  * @return bool
13541  */
13542   function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
13543     if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE;
13544     iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
13545     return TRUE;
13546   }
13547 /*********************************************************************************/
13548 /**
13549  * Property Name: FREEBUSY
13550  */
13551 /**
13552  * creates formatted output for calendar component property freebusy
13553  *
13554  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13555  * @since 2.1.23 - 2012-02-16
13556  * @return string
13557  */
13558   function createFreebusy() {
13559     if( empty( $this->freebusy )) return FALSE;
13560     $output = null;
13561     foreach( $this->freebusy as $freebusyPart ) {
13562       if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
13563         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
13564         continue;
13565       }
13566       $attributes = $content = null;
13567       if( isset( $freebusyPart['value']['fbtype'] )) {
13568           $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
13569         unset( $freebusyPart['value']['fbtype'] );
13570         $freebusyPart['value'] = array_values( $freebusyPart['value'] );
13571       }
13572       else
13573         $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
13574       $attributes .= $this->_createParams( $freebusyPart['params'] );
13575       $fno = 1;
13576       $cnt = count( $freebusyPart['value']);
13577       foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
13578         $formatted   = iCalUtilityFunctions::_date2strdate( $freebusyPeriod[0] );
13579         $content .= $formatted;
13580         $content .= '/';
13581         $cnt2 = count( $freebusyPeriod[1]);
13582         if( array_key_exists( 'year', $freebusyPeriod[1] ))      // date-time
13583           $cnt2 = 7;
13584         elseif( array_key_exists( 'week', $freebusyPeriod[1] ))  // duration
13585           $cnt2 = 5;
13586         if(( 7 == $cnt2 )   &&    // period=  -> date-time
13587             isset( $freebusyPeriod[1]['year'] )  &&
13588             isset( $freebusyPeriod[1]['month'] ) &&
13589             isset( $freebusyPeriod[1]['day'] )) {
13590           $content .= iCalUtilityFunctions::_date2strdate( $freebusyPeriod[1] );
13591         }
13592         else {                                  // period=  -> dur-time
13593           $content .= iCalUtilityFunctions::_duration2str( $freebusyPeriod[1] );
13594         }
13595         if( $fno < $cnt )
13596           $content .= ',';
13597         $fno++;
13598       }
13599       $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
13600     }
13601     return $output;
13602   }
13603 /**
13604  * set calendar component property freebusy
13605  *
13606  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13607  * @since 2.10.30 - 2012-01-16
13608  * @param string $fbType
13609  * @param array $fbValues
13610  * @param array $params, optional
13611  * @param integer $index, optional
13612  * @return bool
13613  */
13614   function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
13615     if( empty( $fbValues )) {
13616       if( $this->getConfig( 'allowEmpty' )) {
13617         iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index );
13618         return TRUE;
13619       }
13620       else
13621         return FALSE;
13622     }
13623     $fbType = strtoupper( $fbType );
13624     if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
+13625        ( 'X-' != substr( $fbType, 0, 2 )))
13626       $fbType = 'BUSY';
13627     $input = array( 'fbtype' => $fbType );
13628     foreach( $fbValues as $fbPeriod ) {   // periods => period
13629       if( empty( $fbPeriod ))
13630         continue;
13631       $freebusyPeriod = array();
13632       foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
13633         $freebusyPairMember = array();
13634         if( is_array( $fbMember )) {
13635           if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
13636             $freebusyPairMember       = iCalUtilityFunctions::_chkDateArr( $fbMember, 7 );
13637             $freebusyPairMember['tz'] = 'Z';
13638           }
13639           elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
13640             $freebusyPairMember       = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
13641             $freebusyPairMember['tz'] = 'Z';
13642           }
13643           else {                                         // array format duration
13644             $freebusyPairMember = iCalUtilityFunctions::_duration2arr( $fbMember );
13645           }
13646         }
13647         elseif(( 3 <= strlen( trim( $fbMember ))) &&    // string format duration
+13648                ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
13649           if( 'P' != $fbMember{0} )
13650             $fbmember = substr( $fbMember, 1 );
13651           $freebusyPairMember = iCalUtilityFunctions::_durationStr2arr( $fbMember );
13652         }
13653         elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
13654           $freebusyPairMember       = iCalUtilityFunctions::_strdate2date( $fbMember, 7 );
13655           unset( $freebusyPairMember['unparsedtext'] );
13656           $freebusyPairMember['tz'] = 'Z';
13657         }
13658         $freebusyPeriod[]   = $freebusyPairMember;
13659       }
13660       $input[]              = $freebusyPeriod;
13661     }
13662     iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
13663     return TRUE;
13664   }
13665 /*********************************************************************************/
13666 /**
13667  * Property Name: GEO
13668  */
13669 /**
13670  * creates formatted output for calendar component property geo
13671  *
13672  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13673  * @since 2.12.6 - 2012-04-21
13674  * @return string
13675  */
13676   function createGeo() {
13677     if( empty( $this->geo )) return FALSE;
13678     if( empty( $this->geo['value'] ))
13679       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
13680     $attributes = $this->_createParams( $this->geo['params'] );
13681     if( 0.0 < $this->geo['value']['latitude'] )
13682       $sign   = '+';
13683     else
13684       $sign   = ( 0.0 > $this->geo['value']['latitude'] ) ? '-' : '';
13685     $content  = $sign.sprintf( "%09.6f", abs( $this->geo['value']['latitude'] ));       // sprintf && lpad && float && sign !"#¤%&/(
13686     $content  = rtrim( rtrim( $content, '0' ), '.' );
13687     if( 0.0 < $this->geo['value']['longitude'] )
13688       $sign   = '+';
13689     else
13690       $sign   = ( 0.0 > $this->geo['value']['longitude'] ) ? '-' : '';
13691     $content .= ';'.$sign.sprintf( '%8.6f', abs( $this->geo['value']['longitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
13692     $content  = rtrim( rtrim( $content, '0' ), '.' );
13693     return $this->_createElement( 'GEO', $attributes, $content );
13694   }
13695 /**
13696  * set calendar component property geo
13697  *
13698  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13699  * @since 2.12.5 - 2012-04-21
13700  * @param float $latitude
13701  * @param float $longitude
13702  * @param array $params optional
13703  * @return bool
13704  */
13705   function setGeo( $latitude, $longitude, $params=FALSE ) {
13706     if(( !empty( $latitude )  || ( 0 == $latitude )) &&
+13707        ( !empty( $longitude ) || ( 0 == $longitude ))) {
13708       if( !is_array( $this->geo )) $this->geo = array();
13709       $this->geo['value']['latitude']  = (float) $latitude;
13710       $this->geo['value']['longitude'] = (float) $longitude;
13711       $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
13712     }
13713     elseif( $this->getConfig( 'allowEmpty' ))
13714       $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
13715     else
13716       return FALSE;
13717     return TRUE;
13718   }
13719 /*********************************************************************************/
13720 /**
13721  * Property Name: LAST-MODIFIED
13722  */
13723 /**
13724  * creates formatted output for calendar component property last-modified
13725  *
13726  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13727  * @since 2.4.8 - 2008-10-21
13728  * @return string
13729  */
13730   function createLastModified() {
13731     if( empty( $this->lastmodified )) return FALSE;
13732     $attributes = $this->_createParams( $this->lastmodified['params'] );
13733     $formatted  = iCalUtilityFunctions::_date2strdate( $this->lastmodified['value'], 7 );
13734     return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
13735   }
13736 /**
13737  * set calendar component property completed
13738  *
13739  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13740  * @since 2.4.8 - 2008-10-23
13741  * @param mixed $year optional
13742  * @param mixed $month optional
13743  * @param int $day optional
13744  * @param int $hour optional
13745  * @param int $min optional
13746  * @param int $sec optional
13747  * @param array $params optional
13748  * @return boll
13749  */
13750   function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
13751     if( empty( $year ))
13752       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
13753     $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
13754     return TRUE;
13755   }
13756 /*********************************************************************************/
13757 /**
13758  * Property Name: LOCATION
13759  */
13760 /**
13761  * creates formatted output for calendar component property location
13762  *
13763  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13764  * @since 2.16.2 - 2012-12-18
13765  * @return string
13766  */
13767   function createLocation() {
13768     if( empty( $this->location )) return FALSE;
13769     if( empty( $this->location['value'] ))
13770       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
13771     $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
13772     $content    = iCalUtilityFunctions::_strrep( $this->location['value'], $this->format, $this->nl );
13773     return $this->_createElement( 'LOCATION', $attributes, $content );
13774   }
13775 /**
13776  * set calendar component property location
13777  '
13778  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13779  * @since 2.4.8 - 2008-11-04
13780  * @param string $value
13781  * @param array params optional
13782  * @return bool
13783  */
13784   function setLocation( $value, $params=FALSE ) {
13785     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
13786     $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
13787     return TRUE;
13788   }
13789 /*********************************************************************************/
13790 /**
13791  * Property Name: ORGANIZER
13792  */
13793 /**
13794  * creates formatted output for calendar component property organizer
13795  *
13796  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13797  * @since 2.6.33 - 2010-12-17
13798  * @return string
13799  */
13800   function createOrganizer() {
13801     if( empty( $this->organizer )) return FALSE;
13802     if( empty( $this->organizer['value'] ))
13803       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
13804     $attributes = $this->_createParams( $this->organizer['params']
+13805                                       , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
13806     return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
13807   }
13808 /**
13809  * set calendar component property organizer
13810  *
13811  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13812  * @since 2.12.18 - 2012-07-13
13813  * @param string $value
13814  * @param array params optional
13815  * @return bool
13816  */
13817   function setOrganizer( $value, $params=FALSE ) {
13818     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
13819     if( !empty( $value )) {
13820       if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
13821         $value = 'MAILTO:'.$value;
13822       elseif( !empty( $value ))
13823         $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
13824       $value = str_replace( 'mailto:', 'MAILTO:', $value );
13825     }
13826     $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
13827     if( isset( $this->organizer['params']['SENT-BY'] )){
13828       if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
13829         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
13830       else
13831         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
13832     }
13833     return TRUE;
13834   }
13835 /*********************************************************************************/
13836 /**
13837  * Property Name: PERCENT-COMPLETE
13838  */
13839 /**
13840  * creates formatted output for calendar component property percent-complete
13841  *
13842  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13843  * @since 2.9.3 - 2011-05-14
13844  * @return string
13845  */
13846   function createPercentComplete() {
13847     if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
13848     if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
13849       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
13850     $attributes = $this->_createParams( $this->percentcomplete['params'] );
13851     return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
13852   }
13853 /**
13854  * set calendar component property percent-complete
13855  *
13856  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13857  * @since 2.9.3 - 2011-05-14
13858  * @param int $value
13859  * @param array $params optional
13860  * @return bool
13861  */
13862   function setPercentComplete( $value, $params=FALSE ) {
13863     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
13864     $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
13865     return TRUE;
13866   }
13867 /*********************************************************************************/
13868 /**
13869  * Property Name: PRIORITY
13870  */
13871 /**
13872  * creates formatted output for calendar component property priority
13873  *
13874  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13875  * @since 2.9.3 - 2011-05-14
13876  * @return string
13877  */
13878   function createPriority() {
13879     if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
13880     if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
13881       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
13882     $attributes = $this->_createParams( $this->priority['params'] );
13883     return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
13884   }
13885 /**
13886  * set calendar component property priority
13887  *
13888  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13889  * @since 2.9.3 - 2011-05-14
13890  * @param int $value
13891  * @param array $params optional
13892  * @return bool
13893  */
13894   function setPriority( $value, $params=FALSE  ) {
13895     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
13896     $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
13897     return TRUE;
13898   }
13899 /*********************************************************************************/
13900 /**
13901  * Property Name: RDATE
13902  */
13903 /**
13904  * creates formatted output for calendar component property rdate
13905  *
13906  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13907  * @since 2.16.9 - 2013-01-09
13908  * @return string
13909  */
13910   function createRdate() {
13911     if( empty( $this->rdate )) return FALSE;
13912     $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
13913     $output = null;
13914     $rdates = array();
13915     foreach( $this->rdate as $rpix => $theRdate ) {
13916       if( empty( $theRdate['value'] )) {
13917         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
13918         continue;
13919       }
13920       if( $utctime  )
13921         unset( $theRdate['params']['TZID'] );
13922       if( 1 < count( $theRdate['value'] ))
13923         usort( $theRdate['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
13924       $rdates[] = $theRdate;
13925     }
13926     if( 1 < count( $rdates ))
13927       usort( $rdates, array( 'iCalUtilityFunctions', '_sortRdate2' ));
13928     foreach( $rdates as $rpix => $theRdate ) {
13929       $attributes = $this->_createParams( $theRdate['params'] );
13930       $cnt = count( $theRdate['value'] );
13931       $content = null;
13932       $rno = 1;
13933       foreach( $theRdate['value'] as $rix => $rdatePart ) {
13934         $contentPart = null;
13935         if( is_array( $rdatePart ) &&
13936             isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
13937           if( $utctime )
13938             unset( $rdatePart[0]['tz'] );
13939           $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[0] ); // PERIOD part 1
13940           if( $utctime || !empty( $theRdate['params']['TZID'] ))
13941             $formatted = str_replace( 'Z', '', $formatted);
13942           $contentPart .= $formatted;
13943           $contentPart .= '/';
13944           $cnt2 = count( $rdatePart[1]);
13945           if( array_key_exists( 'year', $rdatePart[1] )) {
13946             if( array_key_exists( 'hour', $rdatePart[1] ))
13947               $cnt2 = 7;                                      // date-time
13948             else
13949               $cnt2 = 3;                                      // date
13950           }
13951           elseif( array_key_exists( 'week', $rdatePart[1] ))  // duration
13952             $cnt2 = 5;
13953           if(( 7 == $cnt2 )   &&    // period=  -> date-time
13954               isset( $rdatePart[1]['year'] )  &&
13955               isset( $rdatePart[1]['month'] ) &&
13956               isset( $rdatePart[1]['day'] )) {
13957             if( $utctime )
13958               unset( $rdatePart[1]['tz'] );
13959             $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[1] ); // PERIOD part 2
13960             if( $utctime || !empty( $theRdate['params']['TZID'] ))
13961               $formatted = str_replace( 'Z', '', $formatted );
13962            $contentPart .= $formatted;
13963           }
13964           else {                                  // period=  -> dur-time
13965             $contentPart .= iCalUtilityFunctions::_duration2str( $rdatePart[1] );
13966           }
13967         } // PERIOD end
13968         else { // SINGLE date start
13969           if( $utctime )
13970             unset( $rdatePart['tz'] );
13971           $parno = ( isset( $theRdate['params']['VALUE'] ) && ( 'DATE' == isset( $theRdate['params']['VALUE'] ))) ? 3 : null;
13972           $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart, $parno );
13973           if( $utctime || !empty( $theRdate['params']['TZID'] ))
13974             $formatted = str_replace( 'Z', '', $formatted);
13975           $contentPart .= $formatted;
13976         }
13977         $content .= $contentPart;
13978         if( $rno < $cnt )
13979           $content .= ',';
13980         $rno++;
13981       }
13982       $output    .= $this->_createElement( 'RDATE', $attributes, $content );
13983     }
13984     return $output;
13985   }
13986 /**
13987  * set calendar component property rdate
13988  *
13989  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
13990  * @since 2.14.1 - 2012-10-04
13991  * @param array $rdates
13992  * @param array $params, optional
13993  * @param integer $index, optional
13994  * @return bool
13995  */
13996   function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
13997     if( empty( $rdates )) {
13998       if( $this->getConfig( 'allowEmpty' )) {
13999         iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index );
14000         return TRUE;
14001       }
14002       else
14003         return FALSE;
14004     }
14005     $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
14006     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
14007       unset( $input['params']['TZID'] );
14008       $input['params']['VALUE'] = 'DATE-TIME';
14009     }
14010     $zArr = array( 'GMT', 'UTC', 'Z' );
14011     $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
14012             /*  check if PERIOD, if not set */
14013     if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
14014           isset( $rdates[0] )    && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
14015           isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
+14016     (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
+14017                                       iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
+14018                                     ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
+14019      ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
14020       $input['params']['VALUE'] = 'PERIOD';
14021             /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
14022     $date  = reset( $rdates );
14023     if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
14024       $date  = reset( $date );
14025     iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
14026     iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
14027     foreach( $rdates as $rpix => $theRdate ) {
14028       $inputa = null;
14029       iCalUtilityFunctions::_strDate2arr( $theRdate );
14030       if( is_array( $theRdate )) {
14031         if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
14032           foreach( $theRdate as $rix => $rPeriod ) {
14033             iCalUtilityFunctions::_strDate2arr( $theRdate );
14034             if( is_array( $rPeriod )) {
14035               if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) {    // timestamp
14036                 if( isset( $rPeriod['tz'] ) && !iCalUtilityFunctions::_isOffset( $rPeriod['tz'] )) {
14037                   if( isset( $input['params']['TZID'] ))
14038                     $rPeriod['tz'] = $input['params']['TZID'];
14039                   else
14040                     $input['params']['TZID'] = $rPeriod['tz'];
14041                 }
14042                 $inputab = iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno );
14043               }
14044               elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) {
14045                 $d = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_chkDateArr( $rPeriod, $parno ) : iCalUtilityFunctions::_chkDateArr( $rPeriod, 6 );
14046                 if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
14047                   $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
14048                   $inputab = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
14049                   unset( $inputab['unparsedtext'] );
14050                 }
14051                 else
14052                   $inputab = $d;
14053               }
14054               elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
14055                 $inputab   = iCalUtilityFunctions::_strdate2date( reset( $rPeriod ), $parno );
14056                 unset( $inputab['unparsedtext'] );
14057               }
14058               else                                               // array format duration
14059                 $inputab   = iCalUtilityFunctions::_duration2arr( $rPeriod );
14060             }
14061             elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
+14062                    ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
14063               if( 'P' != $rPeriod[0] )
14064                 $rPeriod   = substr( $rPeriod, 1 );
14065               $inputab     = iCalUtilityFunctions::_durationStr2arr( $rPeriod );
14066             }
14067             elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18
14068               $inputab     = iCalUtilityFunctions::_strdate2date( $rPeriod, $parno );
14069               unset( $inputab['unparsedtext'] );
14070             }
14071             if(( 0 == $rpix ) && ( 0 == $rix )) {
14072               if( isset( $inputab['tz'] ) && in_array( strtoupper( $inputab['tz'] ), $zArr )) {
14073                 $inputab['tz'] = 'Z';
14074                 $toZ = TRUE;
14075               }
14076             }
14077             else {
14078               if( isset( $inputa[0]['tz'] ) && ( 'Z' == $inputa[0]['tz'] ) && isset( $inputab['year'] ))
14079                 $inputab['tz'] = 'Z';
14080               else
14081                 unset( $inputab['tz'] );
14082             }
14083             if( $toZ && isset( $inputab['year'] ) )
14084               $inputab['tz'] = 'Z';
14085             $inputa[]      = $inputab;
14086           }
14087         } // PERIOD end
14088         elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp
14089           if( isset( $theRdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theRdate['tz'] )) {
14090             if( isset( $input['params']['TZID'] ))
14091               $theRdate['tz'] = $input['params']['TZID'];
14092             else
14093               $input['params']['TZID'] = $theRdate['tz'];
14094           }
14095           $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
14096         }
14097         else {                                                                  // date[-time]
14098           $inputa = iCalUtilityFunctions::_chkDateArr( $theRdate, $parno );
14099           if( isset( $inputa['tz'] ) && ( 'Z' != $inputa['tz'] ) && iCalUtilityFunctions::_isOffset( $inputa['tz'] )) {
14100             $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $inputa['year'], $inputa['month'], $inputa['day'], $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
14101             $inputa  = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
14102             unset( $inputa['unparsedtext'] );
14103           }
14104         }
14105       }
14106       elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18
14107         $inputa       = iCalUtilityFunctions::_strdate2date( $theRdate, $parno );
14108         unset( $inputa['unparsedtext'] );
14109         if( $toZ )
14110           $inputa['tz'] = 'Z';
14111       }
14112       if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
14113         if(( 0 == $rpix ) && !$toZ )
14114           $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
14115         if( $toZ )
14116           $inputa['tz']    = 'Z';
14117         if( 3 == $parno )
14118           unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
14119         elseif( isset( $inputa['tz'] ))
14120           $inputa['tz']    = (string) $inputa['tz'];
14121         if( isset( $input['params']['TZID'] ) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))))
14122           if( !$toZ )
14123             unset( $inputa['tz'] );
14124       }
14125       $input['value'][]    = $inputa;
14126     }
14127     if( 3 == $parno ) {
14128       $input['params']['VALUE'] = 'DATE';
14129       unset( $input['params']['TZID'] );
14130     }
14131     if( $toZ )
14132       unset( $input['params']['TZID'] );
14133     iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
14134     return TRUE;
14135   }
14136 /*********************************************************************************/
14137 /**
14138  * Property Name: RECURRENCE-ID
14139  */
14140 /**
14141  * creates formatted output for calendar component property recurrence-id
14142  *
14143  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14144  * @since 2.14.4 - 2012-09-26
14145  * @return string
14146  */
14147   function createRecurrenceid() {
14148     if( empty( $this->recurrenceid )) return FALSE;
14149     if( empty( $this->recurrenceid['value'] ))
14150       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
14151     $parno      = ( isset( $this->recurrenceid['params']['VALUE'] ) && ( 'DATE' == $this->recurrenceid['params']['VALUE'] )) ? 3 : null;
14152     $formatted  = iCalUtilityFunctions::_date2strdate( $this->recurrenceid['value'], $parno );
14153     $attributes = $this->_createParams( $this->recurrenceid['params'] );
14154     return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
14155   }
14156 /**
14157  * set calendar component property recurrence-id
14158  *
14159  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14160  * @since 2.9.6 - 2011-05-15
14161  * @param mixed $year
14162  * @param mixed $month optional
14163  * @param int $day optional
14164  * @param int $hour optional
14165  * @param int $min optional
14166  * @param int $sec optional
14167  * @param array $params optional
14168  * @return bool
14169  */
14170   function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
14171     if( empty( $year )) {
14172       if( $this->getConfig( 'allowEmpty' )) {
14173         $this->recurrenceid = array( 'value' => null, 'params' => null );
14174         return TRUE;
14175       }
14176       else
14177         return FALSE;
14178     }
14179     $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
14180     return TRUE;
14181   }
14182 /*********************************************************************************/
14183 /**
14184  * Property Name: RELATED-TO
14185  */
14186 /**
14187  * creates formatted output for calendar component property related-to
14188  *
14189  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14190  * @since 2.16.2 - 2012-12-18
14191  * @return string
14192  */
14193   function createRelatedTo() {
14194     if( empty( $this->relatedto )) return FALSE;
14195     $output = null;
14196     foreach( $this->relatedto as $relation ) {
14197       if( !empty( $relation['value'] ))
14198         $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), iCalUtilityFunctions::_strrep( $relation['value'], $this->format, $this->nl ));
14199       elseif( $this->getConfig( 'allowEmpty' ))
14200         $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
14201     }
14202     return $output;
14203   }
14204 /**
14205  * set calendar component property related-to
14206  *
14207  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14208  * @since 2.11.24 - 2012-02-23
14209  * @param float $relid
14210  * @param array $params, optional
14211  * @param index $index, optional
14212  * @return bool
14213  */
14214   function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
14215     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14216     iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
14217     iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
14218     return TRUE;
14219   }
14220 /*********************************************************************************/
14221 /**
14222  * Property Name: REPEAT
14223  */
14224 /**
14225  * creates formatted output for calendar component property repeat
14226  *
14227  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14228  * @since 2.9.3 - 2011-05-14
14229  * @return string
14230  */
14231   function createRepeat() {
14232     if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
14233     if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
14234       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
14235     $attributes = $this->_createParams( $this->repeat['params'] );
14236     return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
14237   }
14238 /**
14239  * set calendar component property repeat
14240  *
14241  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14242  * @since 2.9.3 - 2011-05-14
14243  * @param string $value
14244  * @param array $params optional
14245  * @return void
14246  */
14247   function setRepeat( $value, $params=FALSE ) {
14248     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14249     $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14250     return TRUE;
14251   }
14252 /*********************************************************************************/
14253 /**
14254  * Property Name: REQUEST-STATUS
14255  */
14256 /**
14257  * creates formatted output for calendar component property request-status
14258  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14259  * @since 2.16.2 - 2012-12-18
14260  * @return string
14261  */
14262   function createRequestStatus() {
14263     if( empty( $this->requeststatus )) return FALSE;
14264     $output = null;
14265     foreach( $this->requeststatus as $rstat ) {
14266       if( empty( $rstat['value']['statcode'] )) {
14267         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
14268         continue;
14269       }
14270       $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
14271       $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
14272       $content    .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['text'], $this->format, $this->nl );
14273       if( isset( $rstat['value']['extdata'] ))
14274         $content  .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['extdata'], $this->format, $this->nl );
14275       $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
14276     }
14277     return $output;
14278   }
14279 /**
14280  * set calendar component property request-status
14281  *
14282  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14283  * @since 2.5.1 - 2008-11-05
14284  * @param float $statcode
14285  * @param string $text
14286  * @param string $extdata, optional
14287  * @param array $params, optional
14288  * @param integer $index, optional
14289  * @return bool
14290  */
14291   function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
14292     if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = null; else return FALSE;
14293     $input              = array( 'statcode' => $statcode, 'text' => $text );
14294     if( $extdata )
14295       $input['extdata'] = $extdata;
14296     iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
14297     return TRUE;
14298   }
14299 /*********************************************************************************/
14300 /**
14301  * Property Name: RESOURCES
14302  */
14303 /**
14304  * creates formatted output for calendar component property resources
14305  *
14306  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14307  * @since 2.16.2 - 2012-12-18
14308  * @return string
14309  */
14310   function createResources() {
14311     if( empty( $this->resources )) return FALSE;
14312     $output = null;
14313     foreach( $this->resources as $resource ) {
14314       if( empty( $resource['value'] )) {
14315         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
14316         continue;
14317       }
14318       $attributes  = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
14319       if( is_array( $resource['value'] )) {
14320         foreach( $resource['value'] as $rix => $resourcePart )
14321           $resource['value'][$rix] = iCalUtilityFunctions::_strrep( $resourcePart, $this->format, $this->nl );
14322         $content   = implode( ',', $resource['value'] );
14323       }
14324       else
14325         $content   = iCalUtilityFunctions::_strrep( $resource['value'], $this->format, $this->nl );
14326       $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
14327     }
14328     return $output;
14329   }
14330 /**
14331  * set calendar component property recources
14332  *
14333  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14334  * @since 2.5.1 - 2008-11-05
14335  * @param mixed $value
14336  * @param array $params, optional
14337  * @param integer $index, optional
14338  * @return bool
14339  */
14340   function setResources( $value, $params=FALSE, $index=FALSE ) {
14341     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14342     iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
14343     return TRUE;
14344   }
14345 /*********************************************************************************/
14346 /**
14347  * Property Name: RRULE
14348  */
14349 /**
14350  * creates formatted output for calendar component property rrule
14351  *
14352  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14353  * @since 2.4.8 - 2008-10-21
14354  * @return string
14355  */
14356   function createRrule() {
14357     if( empty( $this->rrule )) return FALSE;
14358     return $this->_format_recur( 'RRULE', $this->rrule );
14359   }
14360 /**
14361  * set calendar component property rrule
14362  *
14363  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14364  * @since 2.5.1 - 2008-11-05
14365  * @param array $rruleset
14366  * @param array $params, optional
14367  * @param integer $index, optional
14368  * @return void
14369  */
14370   function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
14371     if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE;
14372     iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
14373     return TRUE;
14374   }
14375 /*********************************************************************************/
14376 /**
14377  * Property Name: SEQUENCE
14378  */
14379 /**
14380  * creates formatted output for calendar component property sequence
14381  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14382  * @since 2.9.3 - 2011-05-14
14383  * @return string
14384  */
14385   function createSequence() {
14386     if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
14387     if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
+14388        ( '0' != $this->sequence['value'] ))
14389       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
14390     $attributes = $this->_createParams( $this->sequence['params'] );
14391     return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
14392   }
14393 /**
14394  * set calendar component property sequence
14395  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14396  * @since 2.10.8 - 2011-09-19
14397  * @param int $value optional
14398  * @param array $params optional
14399  * @return bool
14400  */
14401   function setSequence( $value=FALSE, $params=FALSE ) {
14402     if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
14403       $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
14404     $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14405     return TRUE;
14406   }
14407 /*********************************************************************************/
14408 /**
14409  * Property Name: STATUS
14410  */
14411 /**
14412  * creates formatted output for calendar component property status
14413  *
14414  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14415  * @since 2.4.8 - 2008-10-21
14416  * @return string
14417  */
14418   function createStatus() {
14419     if( empty( $this->status )) return FALSE;
14420     if( empty( $this->status['value'] ))
14421       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
14422     $attributes = $this->_createParams( $this->status['params'] );
14423     return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
14424   }
14425 /**
14426  * set calendar component property status
14427  *
14428  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14429  * @since 2.4.8 - 2008-11-04
14430  * @param string $value
14431  * @param array $params optional
14432  * @return bool
14433  */
14434   function setStatus( $value, $params=FALSE ) {
14435     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14436     $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14437     return TRUE;
14438   }
14439 /*********************************************************************************/
14440 /**
14441  * Property Name: SUMMARY
14442  */
14443 /**
14444  * creates formatted output for calendar component property summary
14445  *
14446  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14447  * @since 2.16.2 - 2012-12-18
14448  * @return string
14449  */
14450   function createSummary() {
14451     if( empty( $this->summary )) return FALSE;
14452     if( empty( $this->summary['value'] ))
14453       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
14454     $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
14455     $content    = iCalUtilityFunctions::_strrep( $this->summary['value'], $this->format, $this->nl );
14456     return $this->_createElement( 'SUMMARY', $attributes, $content );
14457   }
14458 /**
14459  * set calendar component property summary
14460  *
14461  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14462  * @since 2.4.8 - 2008-11-04
14463  * @param string $value
14464  * @param string $params optional
14465  * @return bool
14466  */
14467   function setSummary( $value, $params=FALSE ) {
14468     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14469     $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14470     return TRUE;
14471   }
14472 /*********************************************************************************/
14473 /**
14474  * Property Name: TRANSP
14475  */
14476 /**
14477  * creates formatted output for calendar component property transp
14478  *
14479  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14480  * @since 2.4.8 - 2008-10-21
14481  * @return string
14482  */
14483   function createTransp() {
14484     if( empty( $this->transp )) return FALSE;
14485     if( empty( $this->transp['value'] ))
14486       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
14487     $attributes = $this->_createParams( $this->transp['params'] );
14488     return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
14489   }
14490 /**
14491  * set calendar component property transp
14492  *
14493  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14494  * @since 2.4.8 - 2008-11-04
14495  * @param string $value
14496  * @param string $params optional
14497  * @return bool
14498  */
14499   function setTransp( $value, $params=FALSE ) {
14500     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14501     $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14502     return TRUE;
14503   }
14504 /*********************************************************************************/
14505 /**
14506  * Property Name: TRIGGER
14507  */
14508 /**
14509  * creates formatted output for calendar component property trigger
14510  *
14511  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14512  * @since 2.4.16 - 2008-10-21
14513  * @return string
14514  */
14515   function createTrigger() {
14516     if( empty( $this->trigger )) return FALSE;
14517     if( empty( $this->trigger['value'] ))
14518       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
14519     $content = $attributes = null;
14520     if( isset( $this->trigger['value']['year'] )   &&
14521         isset( $this->trigger['value']['month'] )  &&
14522         isset( $this->trigger['value']['day'] ))
14523       $content      .= iCalUtilityFunctions::_date2strdate( $this->trigger['value'] );
14524     else {
14525       if( TRUE !== $this->trigger['value']['relatedStart'] )
14526         $attributes .= $this->intAttrDelimiter.'RELATED=END';
14527       if( $this->trigger['value']['before'] )
14528         $content    .= '-';
14529       $content      .= iCalUtilityFunctions::_duration2str( $this->trigger['value'] );
14530     }
14531     $attributes     .= $this->_createParams( $this->trigger['params'] );
14532     return $this->_createElement( 'TRIGGER', $attributes, $content );
14533   }
14534 /**
14535  * set calendar component property trigger
14536  *
14537  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14538  * @since 2.14.1 - 2012-09-20
14539  * @param mixed $year
14540  * @param mixed $month optional
14541  * @param int $day optional
14542  * @param int $week optional
14543  * @param int $hour optional
14544  * @param int $min optional
14545  * @param int $sec optional
14546  * @param bool $relatedStart optional
14547  * @param bool $before optional
14548  * @param array $params optional
14549  * @return bool
14550  */
14551   function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
14552     if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
14553       if( $this->getConfig( 'allowEmpty' )) {
14554         $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
14555         return TRUE;
14556       }
14557       else
14558         return FALSE;
14559     if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp UTC
14560       $params = iCalUtilityFunctions::_setParams( $month );
14561       $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 );
14562       foreach( $date as $k => $v )
14563         $$k = $v;
14564     }
14565     elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
14566       $params = iCalUtilityFunctions::_setParams( $month );
14567       if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
14568            array_key_exists( 'month', $year ) &&
+14569            array_key_exists( 'day',   $year ))) {  // when this must be a duration
14570         if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
14571           $relatedStart = FALSE;
14572         else
14573           $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
14574         $before         = ( array_key_exists( 'before', $year )       && ( TRUE !== $year['before'] ))       ? FALSE : TRUE;
14575       }
14576       $SSYY  = ( array_key_exists( 'year',  $year )) ? $year['year']  : null;
14577       $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
14578       $day   = ( array_key_exists( 'day',   $year )) ? $year['day']   : null;
14579       $week  = ( array_key_exists( 'week',  $year )) ? $year['week']  : null;
14580       $hour  = ( array_key_exists( 'hour',  $year )) ? $year['hour']  : 0; //null;
14581       $min   = ( array_key_exists( 'min',   $year )) ? $year['min']   : 0; //null;
14582       $sec   = ( array_key_exists( 'sec',   $year )) ? $year['sec']   : 0; //null;
14583       $year  = $SSYY;
14584     }
14585     elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
14586       $params = iCalUtilityFunctions::_setParams( $month );
14587       if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration
14588         $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
14589         $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
14590         if(     'P'  != $year[0] )
14591           $year       = substr( $year, 1 );
14592         $date         = iCalUtilityFunctions::_durationStr2arr( $year);
14593       }
14594       else   // date
14595         $date    = iCalUtilityFunctions::_strdate2date( $year, 7 );
14596       unset( $year, $month, $day, $date['unparsedtext'] );
14597       if( empty( $date ))
14598         $sec = 0;
14599       else
14600         foreach( $date as $k => $v )
14601           $$k = $v;
14602     }
14603     else // single values in function input parameters
14604       $params = iCalUtilityFunctions::_setParams( $params );
14605     if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
14606       $params['VALUE'] = 'DATE-TIME';
14607       $hour = ( $hour ) ? $hour : 0;
14608       $min  = ( $min  ) ? $min  : 0;
14609       $sec  = ( $sec  ) ? $sec  : 0;
14610       $this->trigger = array( 'params' => $params );
14611       $this->trigger['value'] = array( 'year'  => $year
+14612                                      , 'month' => $month
+14613                                      , 'day'   => $day
+14614                                      , 'hour'  => $hour
+14615                                      , 'min'   => $min
+14616                                      , 'sec'   => $sec
+14617                                      , 'tz'    => 'Z' );
14618       return TRUE;
14619     }
14620     elseif(( empty( $year ) && empty( $month )) &&    // duration
+14621            (( !empty( $week ) || ( 0 == $week )) ||
+14622             ( !empty( $day )  || ( 0 == $day  )) ||
+14623             ( !empty( $hour ) || ( 0 == $hour )) ||
+14624             ( !empty( $min )  || ( 0 == $min  )) ||
+14625             ( !empty( $sec )  || ( 0 == $sec  )))) {
14626       unset( $params['RELATED'] ); // set at output creation (END only)
14627       unset( $params['VALUE'] );   // 'DURATION' default
14628       $this->trigger = array( 'params' => $params );
14629       $this->trigger['value']  = array();
14630       if( !empty( $week )) $this->trigger['value']['week'] = $week;
14631       if( !empty( $day  )) $this->trigger['value']['day']  = $day;
14632       if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
14633       if( !empty( $min  )) $this->trigger['value']['min']  = $min;
14634       if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
14635       if( empty( $this->trigger['value'] )) {
14636         $this->trigger['value']['sec'] = 0;
14637         $before                        = FALSE;
14638       }
14639       $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
14640       $before       = ( FALSE !== $before )       ? TRUE : FALSE;
14641       $this->trigger['value']['relatedStart'] = $relatedStart;
14642       $this->trigger['value']['before']       = $before;
14643       return TRUE;
14644     }
14645     return FALSE;
14646   }
14647 /*********************************************************************************/
14648 /**
14649  * Property Name: TZID
14650  */
14651 /**
14652  * creates formatted output for calendar component property tzid
14653  *
14654  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14655  * @since 2.16.2 - 2012-12-18
14656  * @return string
14657  */
14658   function createTzid() {
14659     if( empty( $this->tzid )) return FALSE;
14660     if( empty( $this->tzid['value'] ))
14661       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
14662     $attributes = $this->_createParams( $this->tzid['params'] );
14663     return $this->_createElement( 'TZID', $attributes, iCalUtilityFunctions::_strrep( $this->tzid['value'], $this->format, $this->nl ));
14664   }
14665 /**
14666  * set calendar component property tzid
14667  *
14668  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14669  * @since 2.4.8 - 2008-11-04
14670  * @param string $value
14671  * @param array $params optional
14672  * @return bool
14673  */
14674   function setTzid( $value, $params=FALSE ) {
14675     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14676     $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14677     return TRUE;
14678   }
14679 /*********************************************************************************/
14680 /**
14681  * .. .
14682  * Property Name: TZNAME
14683  */
14684 /**
14685  * creates formatted output for calendar component property tzname
14686  *
14687  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14688  * @since 2.16.2 - 2012-12-18
14689  * @return string
14690  */
14691   function createTzname() {
14692     if( empty( $this->tzname )) return FALSE;
14693     $output = null;
14694     foreach( $this->tzname as $theName ) {
14695       if( !empty( $theName['value'] )) {
14696         $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
14697         $output    .= $this->_createElement( 'TZNAME', $attributes, iCalUtilityFunctions::_strrep( $theName['value'], $this->format, $this->nl ));
14698       }
14699       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
14700     }
14701     return $output;
14702   }
14703 /**
14704  * set calendar component property tzname
14705  *
14706  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14707  * @since 2.5.1 - 2008-11-05
14708  * @param string $value
14709  * @param string $params, optional
14710  * @param integer $index, optional
14711  * @return bool
14712  */
14713   function setTzname( $value, $params=FALSE, $index=FALSE ) {
14714     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14715     iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
14716     return TRUE;
14717   }
14718 /*********************************************************************************/
14719 /**
14720  * Property Name: TZOFFSETFROM
14721  */
14722 /**
14723  * creates formatted output for calendar component property tzoffsetfrom
14724  *
14725  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14726  * @since 2.4.8 - 2008-10-21
14727  * @return string
14728  */
14729   function createTzoffsetfrom() {
14730     if( empty( $this->tzoffsetfrom )) return FALSE;
14731     if( empty( $this->tzoffsetfrom['value'] ))
14732       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
14733     $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
14734     return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
14735   }
14736 /**
14737  * set calendar component property tzoffsetfrom
14738  *
14739  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14740  * @since 2.4.8 - 2008-11-04
14741  * @param string $value
14742  * @param string $params optional
14743  * @return bool
14744  */
14745   function setTzoffsetfrom( $value, $params=FALSE ) {
14746     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14747     $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14748     return TRUE;
14749   }
14750 /*********************************************************************************/
14751 /**
14752  * Property Name: TZOFFSETTO
14753  */
14754 /**
14755  * creates formatted output for calendar component property tzoffsetto
14756  *
14757  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14758  * @since 2.4.8 - 2008-10-21
14759  * @return string
14760  */
14761   function createTzoffsetto() {
14762     if( empty( $this->tzoffsetto )) return FALSE;
14763     if( empty( $this->tzoffsetto['value'] ))
14764       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
14765     $attributes = $this->_createParams( $this->tzoffsetto['params'] );
14766     return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
14767   }
14768 /**
14769  * set calendar component property tzoffsetto
14770  *
14771  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14772  * @since 2.4.8 - 2008-11-04
14773  * @param string $value
14774  * @param string $params optional
14775  * @return bool
14776  */
14777   function setTzoffsetto( $value, $params=FALSE ) {
14778     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14779     $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14780     return TRUE;
14781   }
14782 /*********************************************************************************/
14783 /**
14784  * Property Name: TZURL
14785  */
14786 /**
14787  * creates formatted output for calendar component property tzurl
14788  *
14789  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14790  * @since 2.4.8 - 2008-10-21
14791  * @return string
14792  */
14793   function createTzurl() {
14794     if( empty( $this->tzurl )) return FALSE;
14795     if( empty( $this->tzurl['value'] ))
14796       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
14797     $attributes = $this->_createParams( $this->tzurl['params'] );
14798     return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
14799   }
14800 /**
14801  * set calendar component property tzurl
14802  *
14803  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14804  * @since 2.4.8 - 2008-11-04
14805  * @param string $value
14806  * @param string $params optional
14807  * @return boll
14808  */
14809   function setTzurl( $value, $params=FALSE ) {
14810     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14811     $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14812     return TRUE;
14813   }
14814 /*********************************************************************************/
14815 /**
14816  * Property Name: UID
14817  */
14818 /**
14819  * creates formatted output for calendar component property uid
14820  *
14821  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14822  * @since 0.9.7 - 2006-11-20
14823  * @return string
14824  */
14825   function createUid() {
14826     if( 0 >= count( $this->uid ))
14827       $this->_makeuid();
14828     $attributes = $this->_createParams( $this->uid['params'] );
14829     return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
14830   }
14831 /**
14832  * create an unique id for this calendar component object instance
14833  *
14834  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14835  * @since 2.2.7 - 2007-09-04
14836  * @return void
14837  */
14838   function _makeUid() {
14839     $date   = date('Ymd\THisT');
14840     $unique = substr(microtime(), 2, 4);
14841     $base   = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
14842     $start  = 0;
14843     $end    = strlen( $base ) - 1;
14844     $length = 6;
14845     $str    = null;
14846     for( $p = 0; $p < $length; $p++ )
14847       $unique .= $base{mt_rand( $start, $end )};
14848     $this->uid = array( 'params' => null );
14849     $this->uid['value']  = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
14850   }
14851 /**
14852  * set calendar component property uid
14853  *
14854  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14855  * @since 2.4.8 - 2008-11-04
14856  * @param string $value
14857  * @param string $params optional
14858  * @return bool
14859  */
14860   function setUid( $value, $params=FALSE ) {
14861     if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
14862     $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14863     return TRUE;
14864   }
14865 /*********************************************************************************/
14866 /**
14867  * Property Name: URL
14868  */
14869 /**
14870  * creates formatted output for calendar component property url
14871  *
14872  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14873  * @since 2.4.8 - 2008-10-21
14874  * @return string
14875  */
14876   function createUrl() {
14877     if( empty( $this->url )) return FALSE;
14878     if( empty( $this->url['value'] ))
14879       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
14880     $attributes = $this->_createParams( $this->url['params'] );
14881     return $this->_createElement( 'URL', $attributes, $this->url['value'] );
14882   }
14883 /**
14884  * set calendar component property url
14885  *
14886  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14887  * @since 2.16.7 - 2013-01-11
14888  * @param string $value
14889  * @param string $params optional
14890  * @return bool
14891  */
14892   function setUrl( $value, $params=FALSE ) {
14893     if( !empty( $value )) {
14894       if( !filter_var( $value, FILTER_VALIDATE_URL ) && ( 'urn' != strtolower( substr( $value, 0, 3 ))))
14895         return FALSE;
14896     }
14897     elseif( $this->getConfig( 'allowEmpty' ))
14898       $value = null;
14899     else
14900       return FALSE;
14901     $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
14902     return TRUE;
14903   }
14904 /*********************************************************************************/
14905 /**
14906  * Property Name: x-prop
14907  */
14908 /**
14909  * creates formatted output for calendar component property x-prop
14910  *
14911  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14912  * @since 2.16.2 - 2012-12-18
14913  * @return string
14914  */
14915   function createXprop() {
14916     if( empty( $this->xprop )) return FALSE;
14917     $output = null;
14918     foreach( $this->xprop as $label => $xpropPart ) {
14919       if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
14920         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
14921         continue;
14922       }
14923       $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
14924       if( is_array( $xpropPart['value'] )) {
14925         foreach( $xpropPart['value'] as $pix => $theXpart )
14926           $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->format );
14927         $xpropPart['value']  = implode( ',', $xpropPart['value'] );
14928       }
14929       else
14930         $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
14931       $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
14932     }
14933     return $output;
14934   }
14935 /**
14936  * set calendar component property x-prop
14937  *
14938  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14939  * @since 2.11.9 - 2012-01-16
14940  * @param string $label
14941  * @param mixed $value
14942  * @param array $params optional
14943  * @return bool
14944  */
14945   function setXprop( $label, $value, $params=FALSE ) {
14946     if( empty( $label ))
14947       return FALSE;
14948     if( 'X-' != strtoupper( substr( $label, 0, 2 )))
14949       return FALSE;
14950     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
14951     $xprop           = array( 'value' => $value );
14952     $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
14953     if( !is_array( $this->xprop )) $this->xprop = array();
14954     $this->xprop[strtoupper( $label )] = $xprop;
14955     return TRUE;
14956   }
14957 /*********************************************************************************/
14958 /*********************************************************************************/
14959 /**
14960  * create element format parts
14961  *
14962  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
14963  * @since 2.0.6 - 2006-06-20
14964  * @return string
14965  */
14966   function _createFormat() {
14967     $objectname                   = null;
14968     switch( $this->format ) {
14969       case 'xcal':
14970         $objectname               = ( isset( $this->timezonetype )) ?
14971                                  strtolower( $this->timezonetype )  :  strtolower( $this->objName );
14972         $this->componentStart1    = $this->elementStart1 = '<';
14973         $this->componentStart2    = $this->elementStart2 = '>';
14974         $this->componentEnd1      = $this->elementEnd1   = '</';
14975         $this->componentEnd2      = $this->elementEnd2   = '>'.$this->nl;
14976         $this->intAttrDelimiter   = '<!-- -->';
14977         $this->attributeDelimiter = $this->nl;
14978         $this->valueInit          = null;
14979         break;
14980       default:
14981         $objectname               = ( isset( $this->timezonetype )) ?
14982                                  strtoupper( $this->timezonetype )  :  strtoupper( $this->objName );
14983         $this->componentStart1    = 'BEGIN:';
14984         $this->componentStart2    = null;
14985         $this->componentEnd1      = 'END:';
14986         $this->componentEnd2      = $this->nl;
14987         $this->elementStart1      = null;
14988         $this->elementStart2      = null;
14989         $this->elementEnd1        = null;
14990         $this->elementEnd2        = $this->nl;
14991         $this->intAttrDelimiter   = '<!-- -->';
14992         $this->attributeDelimiter = ';';
14993         $this->valueInit          = ':';
14994         break;
14995     }
14996     return $objectname;
14997   }
14998 /**
14999  * creates formatted output for calendar component property
15000  *
15001  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15002  * @since 2.16.2 - 2012-12-18
15003  * @param string $label property name
15004  * @param string $attributes property attributes
15005  * @param string $content property content (optional)
15006  * @return string
15007  */
15008   function _createElement( $label, $attributes=null, $content=FALSE ) {
15009     switch( $this->format ) {
15010       case 'xcal':
15011         $label = strtolower( $label );
15012         break;
15013       default:
15014         $label = strtoupper( $label );
15015         break;
15016     }
15017     $output = $this->elementStart1.$label;
15018     $categoriesAttrLang = null;
15019     $attachInlineBinary = FALSE;
15020     $attachfmttype      = null;
15021     if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
15022       $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT'
+15023                                , 'ref'      => $label
+15024                                , 'type2'    => '(#PCDATA)' );
15025     }
15026     if( !empty( $attributes ))  {
15027       $attributes  = trim( $attributes );
15028       if ( 'xcal' == $this->format ) {
15029         $attributes2 = explode( $this->intAttrDelimiter, $attributes );
15030         $attributes  = null;
15031         foreach( $attributes2 as $aix => $attribute ) {
15032           $attrKVarr = explode( '=', $attribute );
15033           if( empty( $attrKVarr[0] ))
15034             continue;
15035           if( !isset( $attrKVarr[1] )) {
15036             $attrValue = $attrKVarr[0];
15037             $attrKey   = $aix;
15038           }
15039           elseif( 2 == count( $attrKVarr)) {
15040             $attrKey   = strtolower( $attrKVarr[0] );
15041             $attrValue = $attrKVarr[1];
15042           }
15043           else {
15044             $attrKey   = strtolower( $attrKVarr[0] );
15045             unset( $attrKVarr[0] );
15046             $attrValue = implode( '=', $attrKVarr );
15047           }
15048           if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
15049             $attachInlineBinary = TRUE;
15050             if( 'fmttype' == $attrKey )
15051               $attachfmttype = $attrKey.'='.$attrValue;
15052             continue;
15053           }
15054           elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
15055             $categoriesAttrLang = $attrKey.'='.$attrValue;
15056           else {
15057             $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
15058             $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
15059             if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
15060               $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
15061               $attrValue = str_replace( '"', '', $attrValue );
15062             }
15063             $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
15064           }
15065         }
15066       }
15067       else {
15068         $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
15069       }
15070     }
15071     if(( 'xcal' == $this->format) &&
+15072        ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
15073       $pos = strrpos($content, "/");
15074       $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
15075       $this->xcaldecl[] = array( 'xmldecl'  => 'ENTITY'
+15076                                , 'uri'      => $docname
+15077                                , 'ref'      => 'SYSTEM'
+15078                                , 'external' => $content
+15079                                , 'type'     => 'NDATA'
+15080                                , 'type2'    => 'BINERY' );
15081       $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
15082       $attributes .= 'uri="'.$docname.'"';
15083       $content = null;
15084       if( 'attach' == $label ) {
15085         $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
15086         $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
15087         $attributes = null;
15088       }
15089     }
15090     elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
15091       $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
15092     }
15093     $output .= $attributes;
15094     if( !$content && ( '0' != $content )) {
15095       switch( $this->format ) {
15096         case 'xcal':
15097           $output .= ' /';
15098           $output .= $this->elementStart2.$this->nl;
15099           return $output;
15100           break;
15101         default:
15102           $output .= $this->elementStart2.$this->valueInit;
15103           return iCalUtilityFunctions::_size75( $output, $this->nl );
15104           break;
15105       }
15106     }
15107     $output .= $this->elementStart2;
15108     $output .= $this->valueInit.$content;
15109     switch( $this->format ) {
15110       case 'xcal':
15111         return $output.$this->elementEnd1.$label.$this->elementEnd2;
15112         break;
15113       default:
15114         return iCalUtilityFunctions::_size75( $output, $this->nl );
15115         break;
15116     }
15117   }
15118 /**
15119  * creates formatted output for calendar component property parameters
15120  *
15121  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15122  * @since 2.10.27 - 2012-01-16
15123  * @param array $params  optional
15124  * @param array $ctrKeys optional
15125  * @return string
15126  */
15127   function _createParams( $params=array(), $ctrKeys=array() ) {
15128     if( !is_array( $params ) || empty( $params ))
15129       $params = array();
15130     $attrLANG = $attr1 = $attr2 = $lang = null;
15131     $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
15132     $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
15133     $CNattrExist = $LANGattrExist = FALSE;
15134     $xparams = array();
15135     foreach( $params as $paramKey => $paramValue ) {
15136       if(( FALSE !== strpos( $paramValue, ':' )) ||
+15137          ( FALSE !== strpos( $paramValue, ';' )) ||
+15138          ( FALSE !== strpos( $paramValue, ',' )))
15139         $paramValue = '"'.$paramValue.'"';
15140       if( ctype_digit( (string) $paramKey )) {
15141         $xparams[]          = $paramValue;
15142         continue;
15143       }
15144       $paramKey             = strtoupper( $paramKey );
15145       if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
15146         $xparams[$paramKey] = $paramValue;
15147       else
15148         $params[$paramKey]  = $paramValue;
15149     }
15150     ksort( $xparams, SORT_STRING );
15151     foreach( $xparams as $paramKey => $paramValue ) {
15152       if( ctype_digit( (string) $paramKey ))
15153         $attr2             .= $this->intAttrDelimiter.$paramValue;
15154       else
15155         $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
15156     }
15157     if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
15158       $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
15159       $attr2                = null;
15160     }
15161     if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
15162       if( !empty( $attr2 )) {
15163         $attr1             .= $attr2;
15164         $attr2              = null;
15165       }
15166       $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
15167     }
15168     if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
15169       $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
15170     if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) {
15171       $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
15172     }
15173     if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
15174       $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
15175     if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
15176       $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
15177     if( isset( $params['CN'] )       && $CNattrKey ) {
15178       $attr1                = $this->intAttrDelimiter.'CN='.$params['CN'];
15179       $CNattrExist          = TRUE;
15180     }
15181     if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) {
15182       $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
15183       $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
15184     }
15185     if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
15186       $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
15187     if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) {
15188       $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
15189       $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
15190     }
15191     if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
15192       $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
15193       $LANGattrExist        = TRUE;
15194     }
15195     if( !$LANGattrExist ) {
15196       $lang = $this->getConfig( 'language' );
15197       if(( $CNattrExist || $LANGattrKey ) && $lang )
15198         $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
15199     }
15200     return $attr1.$attrLANG.$attr2;
15201   }
15202 /**
15203  * creates formatted output for calendar component property data value type recur
15204  *
15205  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15206  * @since 2.14.1 - 2012-10-06
15207  * @param array $recurlabel
15208  * @param array $recurdata
15209  * @return string
15210  */
15211   function _format_recur( $recurlabel, $recurdata ) {
15212     $output = null;
15213     foreach( $recurdata as $therule ) {
15214       if( empty( $therule['value'] )) {
15215         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
15216         continue;
15217       }
15218       $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
15219       $content1  = $content2  = null;
15220       foreach( $therule['value'] as $rulelabel => $rulevalue ) {
15221         switch( $rulelabel ) {
15222           case 'FREQ': {
15223             $content1 .= "FREQ=$rulevalue";
15224             break;
15225           }
15226           case 'UNTIL': {
15227             $parno     = ( isset( $rulevalue['hour'] )) ? 7 : 3;
15228             $content2 .= ';UNTIL='.iCalUtilityFunctions::_date2strdate( $rulevalue, $parno );
15229             break;
15230           }
15231           case 'COUNT':
15232           case 'INTERVAL':
15233           case 'WKST': {
15234             $content2 .= ";$rulelabel=$rulevalue";
15235             break;
15236           }
15237           case 'BYSECOND':
15238           case 'BYMINUTE':
15239           case 'BYHOUR':
15240           case 'BYMONTHDAY':
15241           case 'BYYEARDAY':
15242           case 'BYWEEKNO':
15243           case 'BYMONTH':
15244           case 'BYSETPOS': {
15245             $content2 .= ";$rulelabel=";
15246             if( is_array( $rulevalue )) {
15247               foreach( $rulevalue as $vix => $valuePart ) {
15248                 $content2 .= ( $vix ) ? ',' : null;
15249                 $content2 .= $valuePart;
15250               }
15251             }
15252             else
15253              $content2 .= $rulevalue;
15254             break;
15255           }
15256           case 'BYDAY': {
15257             $content2 .= ";$rulelabel=";
15258             $bydaycnt = 0;
15259             foreach( $rulevalue as $vix => $valuePart ) {
15260               $content21 = $content22 = null;
15261               if( is_array( $valuePart )) {
15262                 $content2 .= ( $bydaycnt ) ? ',' : null;
15263                 foreach( $valuePart as $vix2 => $valuePart2 ) {
15264                   if( 'DAY' != strtoupper( $vix2 ))
15265                       $content21 .= $valuePart2;
15266                   else
15267                     $content22 .= $valuePart2;
15268                 }
15269                 $content2 .= $content21.$content22;
15270                 $bydaycnt++;
15271               }
15272               else {
15273                 $content2 .= ( $bydaycnt ) ? ',' : null;
15274                 if( 'DAY' != strtoupper( $vix ))
15275                     $content21 .= $valuePart;
15276                 else {
15277                   $content22 .= $valuePart;
15278                   $bydaycnt++;
15279                 }
15280                 $content2 .= $content21.$content22;
15281               }
15282             }
15283             break;
15284           }
15285           default: {
15286             $content2 .= ";$rulelabel=$rulevalue";
15287             break;
15288           }
15289         }
15290       }
15291       $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
15292     }
15293     return $output;
15294   }
15295 /**
15296  * check if property not exists within component
15297  *
15298  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15299  * @since 2.5.1 - 2008-10-15
15300  * @param string $propName
15301  * @return bool
15302  */
15303   function _notExistProp( $propName ) {
15304     if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
15305     $propName = strtolower( $propName );
15306     if(     'last-modified'    == $propName )  { if( !isset( $this->lastmodified ))    return TRUE; }
15307     elseif( 'percent-complete' == $propName )  { if( !isset( $this->percentcomplete )) return TRUE; }
15308     elseif( 'recurrence-id'    == $propName )  { if( !isset( $this->recurrenceid ))    return TRUE; }
15309     elseif( 'related-to'       == $propName )  { if( !isset( $this->relatedto ))       return TRUE; }
15310     elseif( 'request-status'   == $propName )  { if( !isset( $this->requeststatus ))   return TRUE; }
15311     elseif((       'x-' != substr($propName,0,2)) && !isset( $this->$propName ))       return TRUE;
15312     return FALSE;
15313   }
15314 /*********************************************************************************/
15315 /*********************************************************************************/
15316 /**
15317  * get general component config variables or info about subcomponents
15318  *
15319  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15320  * @since 2.9.6 - 2011-05-14
15321  * @param mixed $config
15322  * @return value
15323  */
15324   function getConfig( $config = FALSE) {
15325     if( !$config ) {
15326       $return = array();
15327       $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
15328       $return['FORMAT']      = $this->getConfig( 'FORMAT' );
15329       if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
15330         $return['LANGUAGE']  = $lang;
15331       $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
15332       $return['TZTD']        = $this->getConfig( 'TZID' );
15333       $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
15334       return $return;
15335     }
15336     switch( strtoupper( $config )) {
15337       case 'ALLOWEMPTY':
15338         return $this->allowEmpty;
15339         break;
15340       case 'COMPSINFO':
15341         unset( $this->compix );
15342         $info = array();
15343         if( isset( $this->components )) {
15344           foreach( $this->components as $cix => $component ) {
15345             if( empty( $component )) continue;
15346             $info[$cix]['ordno'] = $cix + 1;
15347             $info[$cix]['type']  = $component->objName;
15348             $info[$cix]['uid']   = $component->getProperty( 'uid' );
15349             $info[$cix]['props'] = $component->getConfig( 'propinfo' );
15350             $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
15351           }
15352         }
15353         return $info;
15354         break;
15355       case 'FORMAT':
15356         return $this->format;
15357         break;
15358       case 'LANGUAGE':
15359          // get language for calendar component as defined in [RFC 1766]
15360         return $this->language;
15361         break;
15362       case 'NL':
15363       case 'NEWLINECHAR':
15364         return $this->nl;
15365         break;
15366       case 'PROPINFO':
15367         $output = array();
15368         if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
15369           if( empty( $this->uid['value'] ))   $this->_makeuid();
15370                                               $output['UID']              = 1;
15371           if( empty( $this->dtstamp ))        $this->_makeDtstamp();
15372                                               $output['DTSTAMP']          = 1;
15373         }
15374         if( !empty( $this->summary ))         $output['SUMMARY']          = 1;
15375         if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description );
15376         if( !empty( $this->dtstart ))         $output['DTSTART']          = 1;
15377         if( !empty( $this->dtend ))           $output['DTEND']            = 1;
15378         if( !empty( $this->due ))             $output['DUE']              = 1;
15379         if( !empty( $this->duration ))        $output['DURATION']         = 1;
15380         if( !empty( $this->rrule ))           $output['RRULE']            = count( $this->rrule );
15381         if( !empty( $this->rdate ))           $output['RDATE']            = count( $this->rdate );
15382         if( !empty( $this->exdate ))          $output['EXDATE']           = count( $this->exdate );
15383         if( !empty( $this->exrule ))          $output['EXRULE']           = count( $this->exrule );
15384         if( !empty( $this->action ))          $output['ACTION']           = 1;
15385         if( !empty( $this->attach ))          $output['ATTACH']           = count( $this->attach );
15386         if( !empty( $this->attendee ))        $output['ATTENDEE']         = count( $this->attendee );
15387         if( !empty( $this->categories ))      $output['CATEGORIES']       = count( $this->categories );
15388         if( !empty( $this->class ))           $output['CLASS']            = 1;
15389         if( !empty( $this->comment ))         $output['COMMENT']          = count( $this->comment );
15390         if( !empty( $this->completed ))       $output['COMPLETED']        = 1;
15391         if( !empty( $this->contact ))         $output['CONTACT']          = count( $this->contact );
15392         if( !empty( $this->created ))         $output['CREATED']          = 1;
15393         if( !empty( $this->freebusy ))        $output['FREEBUSY']         = count( $this->freebusy );
15394         if( !empty( $this->geo ))             $output['GEO']              = 1;
15395         if( !empty( $this->lastmodified ))    $output['LAST-MODIFIED']    = 1;
15396         if( !empty( $this->location ))        $output['LOCATION']         = 1;
15397         if( !empty( $this->organizer ))       $output['ORGANIZER']        = 1;
15398         if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
15399         if( !empty( $this->priority ))        $output['PRIORITY']         = 1;
15400         if( !empty( $this->recurrenceid ))    $output['RECURRENCE-ID']    = 1;
15401         if( !empty( $this->relatedto ))       $output['RELATED-TO']       = count( $this->relatedto );
15402         if( !empty( $this->repeat ))          $output['REPEAT']           = 1;
15403         if( !empty( $this->requeststatus ))   $output['REQUEST-STATUS']   = count( $this->requeststatus );
15404         if( !empty( $this->resources ))       $output['RESOURCES']        = count( $this->resources );
15405         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
15406         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
15407         if( !empty( $this->status ))          $output['STATUS']           = 1;
15408         if( !empty( $this->transp ))          $output['TRANSP']           = 1;
15409         if( !empty( $this->trigger ))         $output['TRIGGER']          = 1;
15410         if( !empty( $this->tzid ))            $output['TZID']             = 1;
15411         if( !empty( $this->tzname ))          $output['TZNAME']           = count( $this->tzname );
15412         if( !empty( $this->tzoffsetfrom ))    $output['TZOFFSETFROM']     = 1;
15413         if( !empty( $this->tzoffsetto ))      $output['TZOFFSETTO']       = 1;
15414         if( !empty( $this->tzurl ))           $output['TZURL']            = 1;
15415         if( !empty( $this->url ))             $output['URL']              = 1;
15416         if( !empty( $this->xprop ))           $output['X-PROP']           = count( $this->xprop );
15417         return $output;
15418         break;
15419       case 'SETPROPERTYNAMES':
15420         return array_keys( $this->getConfig( 'propinfo' ));
15421         break;
15422       case 'TZID':
15423         return $this->dtzid;
15424         break;
15425       case 'UNIQUE_ID':
15426         if( empty( $this->unique_id ))
15427           $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
15428         return $this->unique_id;
15429         break;
15430     }
15431   }
15432 /**
15433  * general component config setting
15434  *
15435  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15436  * @since 2.10.18 - 2011-10-28
15437  * @param mixed  $config
15438  * @param string $value
15439  * @param bool   $softUpdate
15440  * @return void
15441  */
15442   function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
15443     if( is_array( $config )) {
15444       $ak = array_keys( $config );
15445       foreach( $ak as $k ) {
15446         if( 'NEWLINECHAR' == strtoupper( $k )) {
15447           if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
15448             return FALSE;
15449           unset( $config[$k] );
15450           break;
15451         }
15452       }
15453       foreach( $config as $cKey => $cValue ) {
15454         if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
15455           return FALSE;
15456       }
15457       return TRUE;
15458     }
15459     $res = FALSE;
15460     switch( strtoupper( $config )) {
15461       case 'ALLOWEMPTY':
15462         $this->allowEmpty = $value;
15463         $subcfg = array( 'ALLOWEMPTY' => $value );
15464         $res    = TRUE;
15465         break;
15466       case 'FORMAT':
15467         $value  = trim( strtolower( $value ));
15468         $this->format = $value;
15469         $this->_createFormat();
15470         $subcfg = array( 'FORMAT' => $value );
15471         $res    = TRUE;
15472         break;
15473       case 'LANGUAGE':
15474          // set language for calendar component as defined in [RFC 1766]
15475         $value  = trim( $value );
15476         if( empty( $this->language ) || !$softUpdate )
15477           $this->language = $value;
15478         $subcfg = array( 'LANGUAGE' => $value );
15479         $res    = TRUE;
15480         break;
15481       case 'NL':
15482       case 'NEWLINECHAR':
15483         $this->nl = $value;
15484         $this->_createFormat();
15485         $subcfg = array( 'NL' => $value );
15486         $res    = TRUE;
15487         break;
15488       case 'TZID':
15489         $this->dtzid = $value;
15490         $subcfg = array( 'TZID' => $value );
15491         $res    = TRUE;
15492         break;
15493       case 'UNIQUE_ID':
15494         $value  = trim( $value );
15495         $this->unique_id = $value;
15496         $subcfg = array( 'UNIQUE_ID' => $value );
15497         $res    = TRUE;
15498         break;
15499       default:  // any unvalid config key.. .
15500         return TRUE;
15501     }
15502     if( !$res ) return FALSE;
15503     if( isset( $subcfg ) && !empty( $this->components )) {
15504       foreach( $subcfg as $cfgkey => $cfgvalue ) {
15505         foreach( $this->components as $cix => $component ) {
15506           $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
15507           if( !$res )
15508             break 2;
15509           $this->components[$cix] = $component->copy(); // PHP4 compliant
15510         }
15511       }
15512     }
15513     return $res;
15514   }
15515 /*********************************************************************************/
15516 /**
15517  * delete component property value
15518  *
15519  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15520  * @since 2.8.8 - 2011-03-15
15521  * @param mixed $propName, bool FALSE => X-property
15522  * @param int   $propix, optional, if specific property is wanted in case of multiply occurences
15523  * @return bool, if successfull delete TRUE
15524  */
15525   function deleteProperty( $propName=FALSE, $propix=FALSE ) {
15526     if( $this->_notExistProp( $propName )) return FALSE;
15527     $propName = strtoupper( $propName );
15528     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
+15529                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
15530       if( !$propix )
15531         $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
15532       $this->propdelix[$propName] = --$propix;
15533     }
15534     $return = FALSE;
15535     switch( $propName ) {
15536       case 'ACTION':
15537         if( !empty( $this->action )) {
15538           $this->action = '';
15539           $return = TRUE;
15540         }
15541         break;
15542       case 'ATTACH':
15543         return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
15544         break;
15545       case 'ATTENDEE':
15546         return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
15547         break;
15548       case 'CATEGORIES':
15549         return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
15550         break;
15551       case 'CLASS':
15552         if( !empty( $this->class )) {
15553           $this->class = '';
15554           $return = TRUE;
15555         }
15556         break;
15557       case 'COMMENT':
15558         return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
15559         break;
15560       case 'COMPLETED':
15561         if( !empty( $this->completed )) {
15562           $this->completed = '';
15563           $return = TRUE;
15564         }
15565         break;
15566       case 'CONTACT':
15567         return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
15568         break;
15569       case 'CREATED':
15570         if( !empty( $this->created )) {
15571           $this->created = '';
15572           $return = TRUE;
15573         }
15574         break;
15575       case 'DESCRIPTION':
15576         return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
15577         break;
15578       case 'DTEND':
15579         if( !empty( $this->dtend )) {
15580           $this->dtend = '';
15581           $return = TRUE;
15582         }
15583         break;
15584       case 'DTSTAMP':
15585         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
15586           return FALSE;
15587         if( !empty( $this->dtstamp )) {
15588           $this->dtstamp = '';
15589           $return = TRUE;
15590         }
15591         break;
15592       case 'DTSTART':
15593         if( !empty( $this->dtstart )) {
15594           $this->dtstart = '';
15595           $return = TRUE;
15596         }
15597         break;
15598       case 'DUE':
15599         if( !empty( $this->due )) {
15600           $this->due = '';
15601           $return = TRUE;
15602         }
15603         break;
15604       case 'DURATION':
15605         if( !empty( $this->duration )) {
15606           $this->duration = '';
15607           $return = TRUE;
15608         }
15609         break;
15610       case 'EXDATE':
15611         return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
15612         break;
15613       case 'EXRULE':
15614         return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
15615         break;
15616       case 'FREEBUSY':
15617         return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
15618         break;
15619       case 'GEO':
15620         if( !empty( $this->geo )) {
15621           $this->geo = '';
15622           $return = TRUE;
15623         }
15624         break;
15625       case 'LAST-MODIFIED':
15626         if( !empty( $this->lastmodified )) {
15627           $this->lastmodified = '';
15628           $return = TRUE;
15629         }
15630         break;
15631       case 'LOCATION':
15632         if( !empty( $this->location )) {
15633           $this->location = '';
15634           $return = TRUE;
15635         }
15636         break;
15637       case 'ORGANIZER':
15638         if( !empty( $this->organizer )) {
15639           $this->organizer = '';
15640           $return = TRUE;
15641         }
15642         break;
15643       case 'PERCENT-COMPLETE':
15644         if( !empty( $this->percentcomplete )) {
15645           $this->percentcomplete = '';
15646           $return = TRUE;
15647         }
15648         break;
15649       case 'PRIORITY':
15650         if( !empty( $this->priority )) {
15651           $this->priority = '';
15652           $return = TRUE;
15653         }
15654         break;
15655       case 'RDATE':
15656         return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
15657         break;
15658       case 'RECURRENCE-ID':
15659         if( !empty( $this->recurrenceid )) {
15660           $this->recurrenceid = '';
15661           $return = TRUE;
15662         }
15663         break;
15664       case 'RELATED-TO':
15665         return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
15666         break;
15667       case 'REPEAT':
15668         if( !empty( $this->repeat )) {
15669           $this->repeat = '';
15670           $return = TRUE;
15671         }
15672         break;
15673       case 'REQUEST-STATUS':
15674         return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
15675         break;
15676       case 'RESOURCES':
15677         return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
15678         break;
15679       case 'RRULE':
15680         return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
15681         break;
15682       case 'SEQUENCE':
15683         if( !empty( $this->sequence )) {
15684           $this->sequence = '';
15685           $return = TRUE;
15686         }
15687         break;
15688       case 'STATUS':
15689         if( !empty( $this->status )) {
15690           $this->status = '';
15691           $return = TRUE;
15692         }
15693         break;
15694       case 'SUMMARY':
15695         if( !empty( $this->summary )) {
15696           $this->summary = '';
15697           $return = TRUE;
15698         }
15699         break;
15700       case 'TRANSP':
15701         if( !empty( $this->transp )) {
15702           $this->transp = '';
15703           $return = TRUE;
15704         }
15705         break;
15706       case 'TRIGGER':
15707         if( !empty( $this->trigger )) {
15708           $this->trigger = '';
15709           $return = TRUE;
15710         }
15711         break;
15712       case 'TZID':
15713         if( !empty( $this->tzid )) {
15714           $this->tzid = '';
15715           $return = TRUE;
15716         }
15717         break;
15718       case 'TZNAME':
15719         return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
15720         break;
15721       case 'TZOFFSETFROM':
15722         if( !empty( $this->tzoffsetfrom )) {
15723           $this->tzoffsetfrom = '';
15724           $return = TRUE;
15725         }
15726         break;
15727       case 'TZOFFSETTO':
15728         if( !empty( $this->tzoffsetto )) {
15729           $this->tzoffsetto = '';
15730           $return = TRUE;
15731         }
15732         break;
15733       case 'TZURL':
15734         if( !empty( $this->tzurl )) {
15735           $this->tzurl = '';
15736           $return = TRUE;
15737         }
15738         break;
15739       case 'UID':
15740         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
15741           return FALSE;
15742         if( !empty( $this->uid )) {
15743           $this->uid = '';
15744           $return = TRUE;
15745         }
15746         break;
15747       case 'URL':
15748         if( !empty( $this->url )) {
15749           $this->url = '';
15750           $return = TRUE;
15751         }
15752         break;
15753       default:
15754         $reduced = '';
15755         if( $propName != 'X-PROP' ) {
15756           if( !isset( $this->xprop[$propName] )) return FALSE;
15757           foreach( $this->xprop as $k => $a ) {
15758             if(( $k != $propName ) && !empty( $a ))
15759               $reduced[$k] = $a;
15760           }
15761         }
15762         else {
15763           if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
15764           $xpropno = 0;
15765           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
15766             if( $propix != $xpropno )
15767               $reduced[$xpropkey] = $xpropvalue;
15768             $xpropno++;
15769           }
15770         }
15771         $this->xprop = $reduced;
15772         if( empty( $this->xprop )) {
15773           unset( $this->propdelix[$propName] );
15774           return FALSE;
15775         }
15776         return TRUE;
15777     }
15778     return $return;
15779   }
15780 /*********************************************************************************/
15781 /**
15782  * delete component property value, fixing components with multiple occurencies
15783  *
15784  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15785  * @since 2.8.8 - 2011-03-15
15786  * @param array $multiprop, reference to a component property
15787  * @param int   $propix, reference to removal counter
15788  * @return bool TRUE
15789  */
15790   function deletePropertyM( & $multiprop, & $propix ) {
15791     if( isset( $multiprop[$propix] ))
15792       unset( $multiprop[$propix] );
15793     if( empty( $multiprop )) {
15794       $multiprop = '';
15795       unset( $propix );
15796       return FALSE;
15797     }
15798     else
15799       return TRUE;
15800   }
15801 /**
15802  * get component property value/params
15803  *
15804  * if property has multiply values, consequtive function calls are needed
15805  *
15806  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
15807  * @since 2.12.4 - 2012-04-22
15808  * @param string $propName, optional
15809  * @param int @propix, optional, if specific property is wanted in case of multiply occurences
15810  * @param bool $inclParam=FALSE
15811  * @param bool $specform=FALSE
15812  * @return mixed
15813  */
15814   function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
15815     if( 'GEOLOCATION' == strtoupper( $propName )) {
15816       $content = $this->getProperty( 'LOCATION' );
15817       $content = ( !empty( $content )) ? $content.' ' : '';
15818       if(( FALSE === ( $geo     = $this->getProperty( 'GEO' ))) || empty( $geo ))
15819         return FALSE;
15820       if( 0.0 < $geo['latitude'] )
15821         $sign   = '+';
15822       else
15823         $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
15824       $content .= $sign.sprintf( "%09.6f", abs( $geo['latitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
15825       $content  = rtrim( rtrim( $content, '0' ), '.' );
15826       if( 0.0 < $geo['longitude'] )
15827         $sign   = '+';
15828       else
15829        $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
15830       return $content.$sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';   // sprintf && lpad && float && sign !"#¤%&/(
15831     }
15832     if( $this->_notExistProp( $propName )) return FALSE;
15833     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
15834     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
+15835                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
15836       if( !$propix )
15837         $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
15838       $this->propix[$propName] = --$propix;
15839     }
15840     switch( $propName ) {
15841       case 'ACTION':
15842         if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
15843         break;
15844       case 'ATTACH':
15845         $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
15846         while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
15847           $propix++;
15848         $this->propix[$propName] = $propix;
15849         if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15850         return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
15851         break;
15852       case 'ATTENDEE':
15853         $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
15854         while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
15855           $propix++;
15856         $this->propix[$propName] = $propix;
15857         if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15858         return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
15859         break;
15860       case 'CATEGORIES':
15861         $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
15862         while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
15863           $propix++;
15864         $this->propix[$propName] = $propix;
15865         if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15866         return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
15867         break;
15868       case 'CLASS':
15869         if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
15870         break;
15871       case 'COMMENT':
15872         $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
15873         while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
15874           $propix++;
15875         $this->propix[$propName] = $propix;
15876         if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15877         return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
15878         break;
15879       case 'COMPLETED':
15880         if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
15881         break;
15882       case 'CONTACT':
15883         $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
15884         while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
15885           $propix++;
15886         $this->propix[$propName] = $propix;
15887         if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15888         return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
15889         break;
15890       case 'CREATED':
15891         if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
15892         break;
15893       case 'DESCRIPTION':
15894         $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
15895         while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
15896           $propix++;
15897         $this->propix[$propName] = $propix;
15898         if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15899         return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
15900         break;
15901       case 'DTEND':
15902         if( !empty( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
15903         break;
15904       case 'DTSTAMP':
15905         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
15906           return;
15907         if( !isset( $this->dtstamp['value'] ))
15908           $this->_makeDtstamp();
15909         return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
15910         break;
15911       case 'DTSTART':
15912         if( !empty( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
15913         break;
15914       case 'DUE':
15915         if( !empty( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
15916         break;
15917       case 'DURATION':
15918         if( !isset( $this->duration['value'] )) return FALSE;
15919         $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
15920         return ( $inclParam ) ? array( 'value' => $value, 'params' =>  $this->duration['params'] ) : $value;
15921         break;
15922       case 'EXDATE':
15923         $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
15924         while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
15925           $propix++;
15926         $this->propix[$propName] = $propix;
15927         if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15928         return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
15929         break;
15930       case 'EXRULE':
15931         $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
15932         while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
15933           $propix++;
15934         $this->propix[$propName] = $propix;
15935         if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15936         return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
15937         break;
15938       case 'FREEBUSY':
15939         $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
15940         while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
15941           $propix++;
15942         $this->propix[$propName] = $propix;
15943         if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15944         return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
15945         break;
15946       case 'GEO':
15947         if( !empty( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
15948         break;
15949       case 'LAST-MODIFIED':
15950         if( !empty( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
15951         break;
15952       case 'LOCATION':
15953         if( !empty( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
15954         break;
15955       case 'ORGANIZER':
15956         if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
15957         break;
15958       case 'PERCENT-COMPLETE':
15959         if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
15960         break;
15961       case 'PRIORITY':
15962         if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value'];
15963         break;
15964       case 'RDATE':
15965         $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
15966         while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
15967           $propix++;
15968         $this->propix[$propName] = $propix;
15969         if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15970         return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
15971         break;
15972       case 'RECURRENCE-ID':
15973         if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
15974         break;
15975       case 'RELATED-TO':
15976         $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
15977         while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
15978           $propix++;
15979         $this->propix[$propName] = $propix;
15980         if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15981         return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
15982         break;
15983       case 'REPEAT':
15984         if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
15985         break;
15986       case 'REQUEST-STATUS':
15987         $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
15988         while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
15989           $propix++;
15990         $this->propix[$propName] = $propix;
15991         if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
15992         return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
15993         break;
15994       case 'RESOURCES':
15995         $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
15996         while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
15997           $propix++;
15998         $this->propix[$propName] = $propix;
15999         if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
16000         return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
16001         break;
16002       case 'RRULE':
16003         $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
16004         while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
16005           $propix++;
16006         $this->propix[$propName] = $propix;
16007         if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
16008         return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
16009         break;
16010       case 'SEQUENCE':
16011         if( isset( $this->sequence['value'] ) && ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))) return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
16012         break;
16013       case 'STATUS':
16014         if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
16015         break;
16016       case 'SUMMARY':
16017         if( !empty( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
16018         break;
16019       case 'TRANSP':
16020         if( !empty( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
16021         break;
16022       case 'TRIGGER':
16023         if( !empty( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
16024         break;
16025       case 'TZID':
16026         if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
16027         break;
16028       case 'TZNAME':
16029         $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
16030         while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
16031           $propix++;
16032         $this->propix[$propName] = $propix;
16033         if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
16034         return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
16035         break;
16036       case 'TZOFFSETFROM':
16037         if( !empty( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
16038         break;
16039       case 'TZOFFSETTO':
16040         if( !empty( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
16041         break;
16042       case 'TZURL':
16043         if( !empty( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
16044         break;
16045       case 'UID':
16046         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
16047           return FALSE;
16048         if( empty( $this->uid['value'] ))
16049           $this->_makeuid();
16050         return ( $inclParam ) ? $this->uid : $this->uid['value'];
16051         break;
16052       case 'URL':
16053         if( !empty( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
16054         break;
16055       default:
16056         if( $propName != 'X-PROP' ) {
16057           if( !isset( $this->xprop[$propName] )) return FALSE;
16058           return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
16059                                 : array( $propName, $this->xprop[$propName]['value'] );
16060         }
16061         else {
16062           if( empty( $this->xprop )) return FALSE;
16063           $xpropno = 0;
16064           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
16065             if( $propix == $xpropno )
16066               return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
16067                                     : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
16068             else
16069               $xpropno++;
16070           }
16071           return FALSE; // not found ??
16072         }
16073     }
16074     return FALSE;
16075   }
16076 /**
16077  * returns calendar property unique values for 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO' or 'RESOURCES' and for each, number of occurrence
16078  *
16079  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16080  * @since 2.13.4 - 2012-08-07
16081  * @param string $propName
16082  * @param array  $output, incremented result array
16083  */
16084   function _getProperties( $propName, & $output ) {
16085     if( empty( $output ))
16086       $output = array();
16087     if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' )))
16088       return $output;
16089     while( FALSE !== ( $content = $this->getProperty( $propName ))) {
16090       if( empty( $content ))
16091         continue;
16092       if( is_array( $content )) {
16093         foreach( $content as $part ) {
16094           if( FALSE !== strpos( $part, ',' )) {
16095             $part = explode( ',', $part );
16096             foreach( $part as $thePart ) {
16097               $thePart = trim( $thePart );
16098               if( !empty( $thePart )) {
16099                 if( !isset( $output[$thePart] ))
16100                   $output[$thePart] = 1;
16101                 else
16102                   $output[$thePart] += 1;
16103               }
16104             }
16105           }
16106           else {
16107             $part = trim( $part );
16108             if( !isset( $output[$part] ))
16109               $output[$part] = 1;
16110             else
16111               $output[$part] += 1;
16112           }
16113         }
16114       } // end if( is_array( $content ))
16115       elseif( FALSE !== strpos( $content, ',' )) {
16116         $content = explode( ',', $content );
16117         foreach( $content as $thePart ) {
16118           $thePart = trim( $thePart );
16119           if( !empty( $thePart )) {
16120             if( !isset( $output[$thePart] ))
16121               $output[$thePart] = 1;
16122             else
16123               $output[$thePart] += 1;
16124           }
16125         }
16126       } // end elseif( FALSE !== strpos( $content, ',' ))
16127       else {
16128         $content = trim( $content );
16129         if( !empty( $content )) {
16130           if( !isset( $output[$content] ))
16131             $output[$content] = 1;
16132           else
16133             $output[$content] += 1;
16134         }
16135       }
16136     }
16137     ksort( $output );
16138   }
16139 /**
16140  * general component property setting
16141  *
16142  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16143  * @since 2.5.1 - 2008-11-05
16144  * @param mixed $args variable number of function arguments,
16145  *                    first argument is ALWAYS component name,
16146  *                    second ALWAYS component value!
16147  * @return void
16148  */
16149   function setProperty() {
16150     $numargs    = func_num_args();
16151     if( 1 > $numargs ) return FALSE;
16152     $arglist    = func_get_args();
16153     if( $this->_notExistProp( $arglist[0] )) return FALSE;
16154     if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
16155       return FALSE;
16156     $arglist[0] = strtoupper( $arglist[0] );
16157     for( $argix=$numargs; $argix < 12; $argix++ ) {
16158       if( !isset( $arglist[$argix] ))
16159         $arglist[$argix] = null;
16160     }
16161     switch( $arglist[0] ) {
16162       case 'ACTION':
16163         return $this->setAction( $arglist[1], $arglist[2] );
16164       case 'ATTACH':
16165         return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] );
16166       case 'ATTENDEE':
16167         return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] );
16168       case 'CATEGORIES':
16169         return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] );
16170       case 'CLASS':
16171         return $this->setClass( $arglist[1], $arglist[2] );
16172       case 'COMMENT':
16173         return $this->setComment( $arglist[1], $arglist[2], $arglist[3] );
16174       case 'COMPLETED':
16175         return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
16176       case 'CONTACT':
16177         return $this->setContact( $arglist[1], $arglist[2], $arglist[3] );
16178       case 'CREATED':
16179         return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
16180       case 'DESCRIPTION':
16181         return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] );
16182       case 'DTEND':
16183         return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
16184       case 'DTSTAMP':
16185         return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
16186       case 'DTSTART':
16187         return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
16188       case 'DUE':
16189         return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
16190       case 'DURATION':
16191         return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
16192       case 'EXDATE':
16193         return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] );
16194       case 'EXRULE':
16195         return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] );
16196       case 'FREEBUSY':
16197         return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
16198       case 'GEO':
16199         return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] );
16200       case 'LAST-MODIFIED':
16201         return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
16202       case 'LOCATION':
16203         return $this->setLocation( $arglist[1], $arglist[2] );
16204       case 'ORGANIZER':
16205         return $this->setOrganizer( $arglist[1], $arglist[2] );
16206       case 'PERCENT-COMPLETE':
16207         return $this->setPercentComplete( $arglist[1], $arglist[2] );
16208       case 'PRIORITY':
16209         return $this->setPriority( $arglist[1], $arglist[2] );
16210       case 'RDATE':
16211         return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] );
16212       case 'RECURRENCE-ID':
16213        return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
16214       case 'RELATED-TO':
16215         return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] );
16216       case 'REPEAT':
16217         return $this->setRepeat( $arglist[1], $arglist[2] );
16218       case 'REQUEST-STATUS':
16219         return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
16220       case 'RESOURCES':
16221         return $this->setResources( $arglist[1], $arglist[2], $arglist[3] );
16222       case 'RRULE':
16223         return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] );
16224       case 'SEQUENCE':
16225         return $this->setSequence( $arglist[1], $arglist[2] );
16226       case 'STATUS':
16227         return $this->setStatus( $arglist[1], $arglist[2] );
16228       case 'SUMMARY':
16229         return $this->setSummary( $arglist[1], $arglist[2] );
16230       case 'TRANSP':
16231         return $this->setTransp( $arglist[1], $arglist[2] );
16232       case 'TRIGGER':
16233         return $this->setTrigger( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8], $arglist[9], $arglist[10], $arglist[11] );
16234       case 'TZID':
16235         return $this->setTzid( $arglist[1], $arglist[2] );
16236       case 'TZNAME':
16237         return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] );
16238       case 'TZOFFSETFROM':
16239         return $this->setTzoffsetfrom( $arglist[1], $arglist[2] );
16240       case 'TZOFFSETTO':
16241         return $this->setTzoffsetto( $arglist[1], $arglist[2] );
16242       case 'TZURL':
16243         return $this->setTzurl( $arglist[1], $arglist[2] );
16244       case 'UID':
16245         return $this->setUid( $arglist[1], $arglist[2] );
16246       case 'URL':
16247         return $this->setUrl( $arglist[1], $arglist[2] );
16248       default:
16249         return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
16250     }
16251     return FALSE;
16252   }
16253 /*********************************************************************************/
16254 /**
16255  * parse component unparsed data into properties
16256  *
16257  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16258  * @since 2.16.2 - 2012-12-18
16259  * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
16260  * @return bool FALSE if error occurs during parsing
16261  *
16262  */
16263   function parse( $unparsedtext=null ) {
16264     $nl = $this->getConfig( 'nl' );
16265     if( !empty( $unparsedtext )) {
16266       if( is_array( $unparsedtext ))
16267         $unparsedtext = implode( '\n'.$nl, $unparsedtext );
16268       $unparsedtext = explode( $nl, iCalUtilityFunctions::convEolChar( $unparsedtext, $nl ));
16269     }
16270     elseif( !isset( $this->unparsed ))
16271       $unparsedtext = array();
16272     else
16273       $unparsedtext = $this->unparsed;
16274             /* skip leading (empty/invalid) lines */
16275     foreach( $unparsedtext as $lix => $line ) {
16276       $tst = trim( $line );
16277       if(( '\n' == $tst ) || empty( $tst ))
16278         unset( $unparsedtext[$lix] );
16279       else
16280         break;
16281     }
16282     $this->unparsed = array();
16283     $comp           = & $this;
16284     $config         = $this->getConfig();
16285     $compsync = $subsync = 0;
16286     foreach ( $unparsedtext as $lix => $line ) {
16287       if( 'END:VALARM'         == strtoupper( substr( $line, 0, 10 ))) {
16288         if( 1 != $subsync ) return FALSE;
16289         $this->components[]     = $comp->copy();
16290         $subsync--;
16291       }
16292       elseif( 'END:DAYLIGHT'   == strtoupper( substr( $line, 0, 12 ))) {
16293         if( 1 != $subsync ) return FALSE;
16294         $this->components[]     = $comp->copy();
16295         $subsync--;
16296       }
16297       elseif( 'END:STANDARD'   == strtoupper( substr( $line, 0, 12 ))) {
16298         if( 1 != $subsync ) return FALSE;
16299         array_unshift( $this->components, $comp->copy());
16300         $subsync--;
16301       }
16302       elseif( 'END:'           == strtoupper( substr( $line, 0, 4 ))) { // end:<component>
16303         if( 1 != $compsync ) return FALSE;
16304         if( 0 < $subsync )
16305           $this->components[]   = $comp->copy();
16306         $compsync--;
16307         break;                       /* skip trailing empty lines */
16308       }
16309       elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 ))) {
16310         $comp = new valarm( $config);
16311         $subsync++;
16312       }
16313       elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) {
16314         $comp = new vtimezone( 'standard', $config );
16315         $subsync++;
16316       }
16317       elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) {
16318         $comp = new vtimezone( 'daylight', $config );
16319         $subsync++;
16320       }
16321       elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))  // begin:<component>
16322         $compsync++;
16323       else
16324         $comp->unparsed[]       = $line;
16325     }
16326     if( 0 < $subsync )
16327       $this->components[]   = $comp->copy();
16328     unset( $config );
16329             /* concatenate property values spread over several lines */
16330     $lastix    = -1;
16331     $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
+16332                       , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
+16333                       , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
+16334                       , 'last-modified', 'location', 'organizer', 'percent-complete'
+16335                       , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
+16336                       , 'request-status', 'resources', 'rrule', 'sequence', 'status'
+16337                       , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
+16338                       , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
16339     $proprows  = array();
16340     for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
16341       $line = rtrim( $this->unparsed[$i], $nl );
16342       while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
16343         $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
16344       $proprows[] = $line;
16345     }
16346             /* parse each property 'line' */
16347     $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
16348     $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
16349     $paramProto4 = array( 'crid:', 'news:', 'pres:' );
16350     foreach( $proprows as $line ) {
16351       if( '\n' == substr( $line, -2 ))
16352         $line = substr( $line, 0, -2 );
16353             /* get propname */
16354       $propname = null;
16355       $cix = 0;
16356       while( isset( $line[$cix] )) {
16357         if( in_array( $line[$cix], array( ':', ';' )))
16358           break;
16359         else
16360           $propname .= $line[$cix];
16361         $cix++;
16362       }
16363       if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
16364         $propname2 = $propname;
16365         $propname  = 'X-';
16366       }
16367       if( !in_array( strtolower( $propname ), $propnames )) // skip non standard property names
16368         continue;
16369             /* rest of the line is opt.params and value */
16370       $line = substr( $line, $cix );
16371             /* separate attributes from value */
16372       $attr         = array();
16373       $attrix       = -1;
16374       $clen         = strlen( $line );
16375       $WithinQuotes = FALSE;
16376       $cix          = 0;
16377       while( FALSE !== substr( $line, $cix, 1 )) {
16378         if(                       (  ':' == $line[$cix] )                         &&
+16379                                   ( substr( $line,$cix,     3 )  != '://' )       &&
+16380            ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
+16381            ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
+16382            ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
+16383                       ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
16384              !$WithinQuotes ) {
16385           $attrEnd = TRUE;
16386           if(( $cix < ( $clen - 4 )) &&
+16387                ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
16388             for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
16389               if( '://' == substr( $line, $c2ix - 2, 3 )) {
16390                 $attrEnd = FALSE;
16391                 break; // an URI with a portnr!!
16392               }
16393             }
16394           }
16395           if( $attrEnd) {
16396             $line = substr( $line, ( $cix + 1 ));
16397             break;
16398           }
16399           $cix++;
16400         }
16401         if( '"' == $line[$cix] )
16402           $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
16403         if( ';' == $line[$cix] )
16404           $attr[++$attrix] = null;
16405         else
16406           $attr[$attrix] .= $line[$cix];
16407         $cix++;
16408       }
16409             /* make attributes in array format */
16410       $propattr = array();
16411       foreach( $attr as $attribute ) {
16412         $attrsplit = explode( '=', $attribute, 2 );
16413         if( 1 < count( $attrsplit ))
16414           $propattr[$attrsplit[0]] = $attrsplit[1];
16415         else
16416           $propattr[] = $attribute;
16417       }
16418             /* call setProperty( $propname.. . */
16419       switch( strtoupper( $propname )) {
16420         case 'ATTENDEE':
16421           foreach( $propattr as $pix => $attr ) {
16422             if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' )))
16423               continue;
16424             $attr2 = explode( ',', $attr );
16425               if( 1 < count( $attr2 ))
16426                 $propattr[$pix] = $attr2;
16427           }
16428           $this->setProperty( $propname, $line, $propattr );
16429           break;
16430         case 'X-':
16431           $propname = ( isset( $propname2 )) ? $propname2 : $propname;
16432           unset( $propname2 );
16433         case 'CATEGORIES':
16434         case 'RESOURCES':
16435           if( FALSE !== strpos( $line, ',' )) {
16436             $content  = array( 0 => '' );
16437             $cix = $lix = 0;
16438             while( FALSE !== substr( $line, $lix, 1 )) {
16439               if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
16440                 $cix++;
16441                 $content[$cix] = '';
16442               }
16443               else
16444                 $content[$cix] .= $line[$lix];
16445               $lix++;
16446             }
16447             if( 1 < count( $content )) {
16448               $content = array_values( $content );
16449               foreach( $content as $cix => $contentPart )
16450                 $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
16451               $this->setProperty( $propname, $content, $propattr );
16452               break;
16453             }
16454             else
16455               $line = reset( $content );
16456           }
16457         case 'COMMENT':
16458         case 'CONTACT':
16459         case 'DESCRIPTION':
16460         case 'LOCATION':
16461         case 'SUMMARY':
16462           if( empty( $line ))
16463             $propattr = null;
16464           $this->setProperty( $propname, iCalUtilityFunctions::_strunrep( $line ), $propattr );
16465           break;
16466         case 'REQUEST-STATUS':
16467           $values    = explode( ';', $line, 3 );
16468           $values[1] = ( !isset( $values[1] )) ? null : iCalUtilityFunctions::_strunrep( $values[1] );
16469           $values[2] = ( !isset( $values[2] )) ? null : iCalUtilityFunctions::_strunrep( $values[2] );
16470           $this->setProperty( $propname
+16471                             , $values[0]  // statcode
+16472                             , $values[1]  // statdesc
+16473                             , $values[2]  // extdata
+16474                             , $propattr );
16475           break;
16476         case 'FREEBUSY':
16477           $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing
16478           unset( $propattr['FBTYPE'] );
16479           $values = explode( ',', $line );
16480           foreach( $values as $vix => $value ) {
16481             $value2 = explode( '/', $value );
16482             if( 1 < count( $value2 ))
16483               $values[$vix] = $value2;
16484           }
16485           $this->setProperty( $propname, $fbtype, $values, $propattr );
16486           break;
16487         case 'GEO':
16488           $value = explode( ';', $line, 2 );
16489           if( 2 > count( $value ))
16490             $value[1] = null;
16491           $this->setProperty( $propname, $value[0], $value[1], $propattr );
16492           break;
16493         case 'EXDATE':
16494           $values = ( !empty( $line )) ? explode( ',', $line ) : null;
16495           $this->setProperty( $propname, $values, $propattr );
16496           break;
16497         case 'RDATE':
16498           if( empty( $line )) {
16499             $this->setProperty( $propname, $line, $propattr );
16500             break;
16501           }
16502           $values = explode( ',', $line );
16503           foreach( $values as $vix => $value ) {
16504             $value2 = explode( '/', $value );
16505             if( 1 < count( $value2 ))
16506               $values[$vix] = $value2;
16507           }
16508           $this->setProperty( $propname, $values, $propattr );
16509           break;
16510         case 'EXRULE':
16511         case 'RRULE':
16512           $values = explode( ';', $line );
16513           $recur = array();
16514           foreach( $values as $value2 ) {
16515             if( empty( $value2 ))
16516               continue; // ;-char in ending position ???
16517             $value3 = explode( '=', $value2, 2 );
16518             $rulelabel = strtoupper( $value3[0] );
16519             switch( $rulelabel ) {
16520               case 'BYDAY': {
16521                 $value4 = explode( ',', $value3[1] );
16522                 if( 1 < count( $value4 )) {
16523                   foreach( $value4 as $v5ix => $value5 ) {
16524                     $value6 = array();
16525                     $dayno = $dayname = null;
16526                     $value5 = trim( (string) $value5 );
16527                     if(( ctype_alpha( substr( $value5, -1 ))) &&
+16528                        ( ctype_alpha( substr( $value5, -2, 1 )))) {
16529                       $dayname = substr( $value5, -2, 2 );
16530                       if( 2 < strlen( $value5 ))
16531                         $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
16532                     }
16533                     if( $dayno )
16534                       $value6[] = $dayno;
16535                     if( $dayname )
16536                       $value6['DAY'] = $dayname;
16537                     $value4[$v5ix] = $value6;
16538                   }
16539                 }
16540                 else {
16541                   $value4 = array();
16542                   $dayno  = $dayname = null;
16543                   $value5 = trim( (string) $value3[1] );
16544                   if(( ctype_alpha( substr( $value5, -1 ))) &&
+16545                      ( ctype_alpha( substr( $value5, -2, 1 )))) {
16546                       $dayname = substr( $value5, -2, 2 );
16547                     if( 2 < strlen( $value5 ))
16548                       $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
16549                   }
16550                   if( $dayno )
16551                     $value4[] = $dayno;
16552                   if( $dayname )
16553                     $value4['DAY'] = $dayname;
16554                 }
16555                 $recur[$rulelabel] = $value4;
16556                 break;
16557               }
16558               default: {
16559                 $value4 = explode( ',', $value3[1] );
16560                 if( 1 < count( $value4 ))
16561                   $value3[1] = $value4;
16562                 $recur[$rulelabel] = $value3[1];
16563                 break;
16564               }
16565             } // end - switch $rulelabel
16566           } // end - foreach( $values.. .
16567           $this->setProperty( $propname, $recur, $propattr );
16568           break;
16569         case 'ACTION':
16570         case 'CLASSIFICATION':
16571         case 'STATUS':
16572         case 'TRANSP':
16573         case 'UID':
16574         case 'TZID':
16575         case 'RELATED-TO':
16576         case 'TZNAME':
16577           $line = iCalUtilityFunctions::_strunrep( $line );
16578         default:
16579           $this->setProperty( $propname, $line, $propattr );
16580           break;
16581       } // end  switch( $propname.. .
16582     } // end - foreach( $proprows.. .
16583     unset( $unparsedtext, $this->unparsed, $proprows );
16584     if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
16585       $ckeys = array_keys( $this->components );
16586       foreach( $ckeys as $ckey ) {
16587         if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
16588           $this->components[$ckey]->parse();
16589         }
16590       }
16591     }
16592     return TRUE;
16593   }
16594 /*********************************************************************************/
16595 /*********************************************************************************/
16596 /**
16597  * return a copy of this component
16598  *
16599  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16600  * @since 2.15.4 - 2012-10-18
16601  * @return object
16602  */
16603   function copy() {
16604     return unserialize( serialize( $this ));
16605  }
16606 /*********************************************************************************/
16607 /*********************************************************************************/
16608 /**
16609  * delete calendar subcomponent from component container
16610  *
16611  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16612  * @since 2.8.8 - 2011-03-15
16613  * @param mixed $arg1 ordno / component type / component uid
16614  * @param mixed $arg2 optional, ordno if arg1 = component type
16615  * @return void
16616  */
16617   function deleteComponent( $arg1, $arg2=FALSE  ) {
16618     if( !isset( $this->components )) return FALSE;
16619     $argType = $index = null;
16620     if ( ctype_digit( (string) $arg1 )) {
16621       $argType = 'INDEX';
16622       $index   = (int) $arg1 - 1;
16623     }
16624     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
16625       $argType = strtolower( $arg1 );
16626       $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
16627     }
16628     $cix2dC = 0;
16629     foreach ( $this->components as $cix => $component) {
16630       if( empty( $component )) continue;
16631       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
16632         unset( $this->components[$cix] );
16633         return TRUE;
16634       }
16635       elseif( $argType == $component->objName ) {
16636         if( $index == $cix2dC ) {
16637           unset( $this->components[$cix] );
16638           return TRUE;
16639         }
16640         $cix2dC++;
16641       }
16642       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
16643         unset( $this->components[$cix] );
16644         return TRUE;
16645       }
16646     }
16647     return FALSE;
16648   }
16649 /**
16650  * get calendar component subcomponent from component container
16651  *
16652  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16653  * @since 2.8.8 - 2011-03-15
16654  * @param mixed $arg1 optional, ordno/component type/ component uid
16655  * @param mixed $arg2 optional, ordno if arg1 = component type
16656  * @return object
16657  */
16658   function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
16659     if( !isset( $this->components )) return FALSE;
16660     $index = $argType = null;
16661     if ( !$arg1 ) {
16662       $argType = 'INDEX';
16663       $index   = $this->compix['INDEX'] =
+16664         ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
16665     }
16666     elseif ( ctype_digit( (string) $arg1 )) {
16667       $argType = 'INDEX';
16668       $index   = (int) $arg1;
16669       unset( $this->compix );
16670     }
16671     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
16672       unset( $this->compix['INDEX'] );
16673       $argType = strtolower( $arg1 );
16674       if( !$arg2 )
16675         $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
16676       else
16677         $index = (int) $arg2;
16678     }
16679     $index  -= 1;
16680     $ckeys = array_keys( $this->components );
16681     if( !empty( $index) && ( $index > end( $ckeys )))
16682       return FALSE;
16683     $cix2gC = 0;
16684     foreach( $this->components as $cix => $component ) {
16685       if( empty( $component )) continue;
16686       if(( 'INDEX' == $argType ) && ( $index == $cix ))
16687         return $component->copy();
16688       elseif( $argType == $component->objName ) {
16689          if( $index == $cix2gC )
16690            return $component->copy();
16691          $cix2gC++;
16692       }
16693       elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
16694         return $component->copy();
16695     }
16696             /* not found.. . */
16697     unset( $this->compix );
16698     return false;
16699   }
16700 /**
16701  * add calendar component as subcomponent to container for subcomponents
16702  *
16703  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16704  * @since 1.x.x - 2007-04-24
16705  * @param object $component calendar component
16706  * @return void
16707  */
16708   function addSubComponent ( $component ) {
16709     $this->setComponent( $component );
16710   }
16711 /**
16712  * create new calendar component subcomponent, already included within component
16713  *
16714  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16715  * @since 2.6.33 - 2011-01-03
16716  * @param string $compType subcomponent type
16717  * @return object (reference)
16718  */
16719   function & newComponent( $compType ) {
16720     $config = $this->getConfig();
16721     $keys   = array_keys( $this->components );
16722     $ix     = end( $keys) + 1;
16723     switch( strtoupper( $compType )) {
16724       case 'ALARM':
16725       case 'VALARM':
16726         $this->components[$ix] = new valarm( $config );
16727         break;
16728       case 'STANDARD':
16729         array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
16730         $ix = 0;
16731         break;
16732       case 'DAYLIGHT':
16733         $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
16734         break;
16735       default:
16736         return FALSE;
16737     }
16738     return $this->components[$ix];
16739   }
16740 /**
16741  * add calendar component as subcomponent to container for subcomponents
16742  *
16743  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16744  * @since 2.8.8 - 2011-03-15
16745  * @param object $component calendar component
16746  * @param mixed $arg1 optional, ordno/component type/ component uid
16747  * @param mixed $arg2 optional, ordno if arg1 = component type
16748  * @return bool
16749  */
16750   function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
16751     if( !isset( $this->components )) return FALSE;
16752     $component->setConfig( $this->getConfig(), FALSE, TRUE );
16753     if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
16754             /* make sure dtstamp and uid is set */
16755       $dummy = $component->getProperty( 'dtstamp' );
16756       $dummy = $component->getProperty( 'uid' );
16757     }
16758     if( !$arg1 ) { // plain insert, last in chain
16759       $this->components[] = $component->copy();
16760       return TRUE;
16761     }
16762     $argType = $index = null;
16763     if ( ctype_digit( (string) $arg1 )) { // index insert/replace
16764       $argType = 'INDEX';
16765       $index   = (int) $arg1 - 1;
16766     }
16767     elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
16768       $argType = strtolower( $arg1 );
16769       $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
16770     }
16771     // else if arg1 is set, arg1 must be an UID
16772     $cix2sC = 0;
16773     foreach ( $this->components as $cix => $component2 ) {
16774       if( empty( $component2 )) continue;
16775       if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
16776         $this->components[$cix] = $component->copy();
16777         return TRUE;
16778       }
16779       elseif( $argType == $component2->objName ) { // component Type index insert/replace
16780         if( $index == $cix2sC ) {
16781           $this->components[$cix] = $component->copy();
16782           return TRUE;
16783         }
16784         $cix2sC++;
16785       }
16786       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
16787         $this->components[$cix] = $component->copy();
16788         return TRUE;
16789       }
16790     }
16791             /* arg1=index and not found.. . insert at index .. .*/
16792     if( 'INDEX' == $argType ) {
16793       $this->components[$index] = $component->copy();
16794       ksort( $this->components, SORT_NUMERIC );
16795     }
16796     else    /* not found.. . insert last in chain anyway .. .*/
16797     $this->components[] = $component->copy();
16798     return TRUE;
16799   }
16800 /**
16801  * creates formatted output for subcomponents
16802  *
16803  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16804  * @since 2.11.20 - 2012-02-06
16805  * @param array $xcaldecl
16806  * @return string
16807  */
16808   function createSubComponent() {
16809     $output = null;
16810     if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order
16811       $stdarr = $dlarr = array();
16812       foreach( $this->components as $component ) {
16813         if( empty( $component ))
16814           continue;
16815         $dt  = $component->getProperty( 'dtstart' );
16816         $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
16817         if( 'standard' == $component->objName ) {
16818           while( isset( $stdarr[$key] ))
16819             $key += 1;
16820           $stdarr[$key] = $component->copy();
16821         }
16822         elseif( 'daylight' == $component->objName ) {
16823           while( isset( $dlarr[$key] ))
16824             $key += 1;
16825           $dlarr[$key] = $component->copy();
16826         }
16827       } // end foreach( $this->components as $component )
16828       $this->components = array();
16829       ksort( $stdarr, SORT_NUMERIC );
16830       foreach( $stdarr as $std )
16831         $this->components[] = $std->copy();
16832       unset( $stdarr );
16833       ksort( $dlarr,  SORT_NUMERIC );
16834       foreach( $dlarr as $dl )
16835         $this->components[] = $dl->copy();
16836       unset( $dlarr );
16837     } // end if( 'vtimezone' == $this->objName )
16838     foreach( $this->components as $component ) {
16839       $component->setConfig( $this->getConfig(), FALSE, TRUE );
16840       $output .= $component->createComponent( $this->xcaldecl );
16841     }
16842     return $output;
16843   }
16844 }
16845 /*********************************************************************************/
16846 /*********************************************************************************/
16847 /**
16848  * class for calendar component VEVENT
16849  *
16850  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16851  * @since 2.5.1 - 2008-10-12
16852  */
16853 class vevent extends calendarComponent {
16854   var $attach;
16855   var $attendee;
16856   var $categories;
16857   var $comment;
16858   var $contact;
16859   var $class;
16860   var $created;
16861   var $description;
16862   var $dtend;
16863   var $dtstart;
16864   var $duration;
16865   var $exdate;
16866   var $exrule;
16867   var $geo;
16868   var $lastmodified;
16869   var $location;
16870   var $organizer;
16871   var $priority;
16872   var $rdate;
16873   var $recurrenceid;
16874   var $relatedto;
16875   var $requeststatus;
16876   var $resources;
16877   var $rrule;
16878   var $sequence;
16879   var $status;
16880   var $summary;
16881   var $transp;
16882   var $url;
16883   var $xprop;
16884             //  component subcomponents container
16885   var $components;
16886 /**
16887  * constructor for calendar component VEVENT object
16888  *
16889  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16890  * @since 2.8.2 - 2011-05-01
16891  * @param  array $config
16892  * @return void
16893  */
16894   function vevent( $config = array()) {
16895     $this->calendarComponent();
16896 
16897     $this->attach          = '';
16898     $this->attendee        = '';
16899     $this->categories      = '';
16900     $this->class           = '';
16901     $this->comment         = '';
16902     $this->contact         = '';
16903     $this->created         = '';
16904     $this->description     = '';
16905     $this->dtstart         = '';
16906     $this->dtend           = '';
16907     $this->duration        = '';
16908     $this->exdate          = '';
16909     $this->exrule          = '';
16910     $this->geo             = '';
16911     $this->lastmodified    = '';
16912     $this->location        = '';
16913     $this->organizer       = '';
16914     $this->priority        = '';
16915     $this->rdate           = '';
16916     $this->recurrenceid    = '';
16917     $this->relatedto       = '';
16918     $this->requeststatus   = '';
16919     $this->resources       = '';
16920     $this->rrule           = '';
16921     $this->sequence        = '';
16922     $this->status          = '';
16923     $this->summary         = '';
16924     $this->transp          = '';
16925     $this->url             = '';
16926     $this->xprop           = '';
16927 
16928     $this->components      = array();
16929 
16930     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
16931                                           $config['language']   = ICAL_LANG;
16932     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
16933     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
16934     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
16935     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
16936     $this->setConfig( $config );
16937 
16938   }
16939 /**
16940  * create formatted output for calendar component VEVENT object instance
16941  *
16942  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16943  * @since 2.10.16 - 2011-10-28
16944  * @param array $xcaldecl
16945  * @return string
16946  */
16947   function createComponent( &$xcaldecl ) {
16948     $objectname    = $this->_createFormat();
16949     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
16950     $component    .= $this->createUid();
16951     $component    .= $this->createDtstamp();
16952     $component    .= $this->createAttach();
16953     $component    .= $this->createAttendee();
16954     $component    .= $this->createCategories();
16955     $component    .= $this->createComment();
16956     $component    .= $this->createContact();
16957     $component    .= $this->createClass();
16958     $component    .= $this->createCreated();
16959     $component    .= $this->createDescription();
16960     $component    .= $this->createDtstart();
16961     $component    .= $this->createDtend();
16962     $component    .= $this->createDuration();
16963     $component    .= $this->createExdate();
16964     $component    .= $this->createExrule();
16965     $component    .= $this->createGeo();
16966     $component    .= $this->createLastModified();
16967     $component    .= $this->createLocation();
16968     $component    .= $this->createOrganizer();
16969     $component    .= $this->createPriority();
16970     $component    .= $this->createRdate();
16971     $component    .= $this->createRrule();
16972     $component    .= $this->createRelatedTo();
16973     $component    .= $this->createRequestStatus();
16974     $component    .= $this->createRecurrenceid();
16975     $component    .= $this->createResources();
16976     $component    .= $this->createSequence();
16977     $component    .= $this->createStatus();
16978     $component    .= $this->createSummary();
16979     $component    .= $this->createTransp();
16980     $component    .= $this->createUrl();
16981     $component    .= $this->createXprop();
16982     $component    .= $this->createSubComponent();
16983     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
16984     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
16985       foreach( $this->xcaldecl as $localxcaldecl )
16986         $xcaldecl[] = $localxcaldecl;
16987     }
16988     return $component;
16989   }
16990 }
16991 /*********************************************************************************/
16992 /*********************************************************************************/
16993 /**
16994  * class for calendar component VTODO
16995  *
16996  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
16997  * @since 2.5.1 - 2008-10-12
16998  */
16999 class vtodo extends calendarComponent {
17000   var $attach;
17001   var $attendee;
17002   var $categories;
17003   var $comment;
17004   var $completed;
17005   var $contact;
17006   var $class;
17007   var $created;
17008   var $description;
17009   var $dtstart;
17010   var $due;
17011   var $duration;
17012   var $exdate;
17013   var $exrule;
17014   var $geo;
17015   var $lastmodified;
17016   var $location;
17017   var $organizer;
17018   var $percentcomplete;
17019   var $priority;
17020   var $rdate;
17021   var $recurrenceid;
17022   var $relatedto;
17023   var $requeststatus;
17024   var $resources;
17025   var $rrule;
17026   var $sequence;
17027   var $status;
17028   var $summary;
17029   var $url;
17030   var $xprop;
17031             //  component subcomponents container
17032   var $components;
17033 /**
17034  * constructor for calendar component VTODO object
17035  *
17036  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17037  * @since 2.8.2 - 2011-05-01
17038  * @param array $config
17039  * @return void
17040  */
17041   function vtodo( $config = array()) {
17042     $this->calendarComponent();
17043 
17044     $this->attach          = '';
17045     $this->attendee        = '';
17046     $this->categories      = '';
17047     $this->class           = '';
17048     $this->comment         = '';
17049     $this->completed       = '';
17050     $this->contact         = '';
17051     $this->created         = '';
17052     $this->description     = '';
17053     $this->dtstart         = '';
17054     $this->due             = '';
17055     $this->duration        = '';
17056     $this->exdate          = '';
17057     $this->exrule          = '';
17058     $this->geo             = '';
17059     $this->lastmodified    = '';
17060     $this->location        = '';
17061     $this->organizer       = '';
17062     $this->percentcomplete = '';
17063     $this->priority        = '';
17064     $this->rdate           = '';
17065     $this->recurrenceid    = '';
17066     $this->relatedto       = '';
17067     $this->requeststatus   = '';
17068     $this->resources       = '';
17069     $this->rrule           = '';
17070     $this->sequence        = '';
17071     $this->status          = '';
17072     $this->summary         = '';
17073     $this->url             = '';
17074     $this->xprop           = '';
17075 
17076     $this->components      = array();
17077 
17078     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
17079                                           $config['language']   = ICAL_LANG;
17080     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
17081     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
17082     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
17083     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
17084     $this->setConfig( $config );
17085 
17086   }
17087 /**
17088  * create formatted output for calendar component VTODO object instance
17089  *
17090  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17091  * @since 2.5.1 - 2008-11-07
17092  * @param array $xcaldecl
17093  * @return string
17094  */
17095   function createComponent( &$xcaldecl ) {
17096     $objectname    = $this->_createFormat();
17097     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
17098     $component    .= $this->createUid();
17099     $component    .= $this->createDtstamp();
17100     $component    .= $this->createAttach();
17101     $component    .= $this->createAttendee();
17102     $component    .= $this->createCategories();
17103     $component    .= $this->createClass();
17104     $component    .= $this->createComment();
17105     $component    .= $this->createCompleted();
17106     $component    .= $this->createContact();
17107     $component    .= $this->createCreated();
17108     $component    .= $this->createDescription();
17109     $component    .= $this->createDtstart();
17110     $component    .= $this->createDue();
17111     $component    .= $this->createDuration();
17112     $component    .= $this->createExdate();
17113     $component    .= $this->createExrule();
17114     $component    .= $this->createGeo();
17115     $component    .= $this->createLastModified();
17116     $component    .= $this->createLocation();
17117     $component    .= $this->createOrganizer();
17118     $component    .= $this->createPercentComplete();
17119     $component    .= $this->createPriority();
17120     $component    .= $this->createRdate();
17121     $component    .= $this->createRelatedTo();
17122     $component    .= $this->createRequestStatus();
17123     $component    .= $this->createRecurrenceid();
17124     $component    .= $this->createResources();
17125     $component    .= $this->createRrule();
17126     $component    .= $this->createSequence();
17127     $component    .= $this->createStatus();
17128     $component    .= $this->createSummary();
17129     $component    .= $this->createUrl();
17130     $component    .= $this->createXprop();
17131     $component    .= $this->createSubComponent();
17132     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
17133     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
17134       foreach( $this->xcaldecl as $localxcaldecl )
17135         $xcaldecl[] = $localxcaldecl;
17136     }
17137     return $component;
17138   }
17139 }
17140 /*********************************************************************************/
17141 /*********************************************************************************/
17142 /**
17143  * class for calendar component VJOURNAL
17144  *
17145  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17146  * @since 2.5.1 - 2008-10-12
17147  */
17148 class vjournal extends calendarComponent {
17149   var $attach;
17150   var $attendee;
17151   var $categories;
17152   var $comment;
17153   var $contact;
17154   var $class;
17155   var $created;
17156   var $description;
17157   var $dtstart;
17158   var $exdate;
17159   var $exrule;
17160   var $lastmodified;
17161   var $organizer;
17162   var $rdate;
17163   var $recurrenceid;
17164   var $relatedto;
17165   var $requeststatus;
17166   var $rrule;
17167   var $sequence;
17168   var $status;
17169   var $summary;
17170   var $url;
17171   var $xprop;
17172 /**
17173  * constructor for calendar component VJOURNAL object
17174  *
17175  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17176  * @since 2.8.2 - 2011-05-01
17177  * @param array $config
17178  * @return void
17179  */
17180   function vjournal( $config = array()) {
17181     $this->calendarComponent();
17182 
17183     $this->attach          = '';
17184     $this->attendee        = '';
17185     $this->categories      = '';
17186     $this->class           = '';
17187     $this->comment         = '';
17188     $this->contact         = '';
17189     $this->created         = '';
17190     $this->description     = '';
17191     $this->dtstart         = '';
17192     $this->exdate          = '';
17193     $this->exrule          = '';
17194     $this->lastmodified    = '';
17195     $this->organizer       = '';
17196     $this->rdate           = '';
17197     $this->recurrenceid    = '';
17198     $this->relatedto       = '';
17199     $this->requeststatus   = '';
17200     $this->rrule           = '';
17201     $this->sequence        = '';
17202     $this->status          = '';
17203     $this->summary         = '';
17204     $this->url             = '';
17205     $this->xprop           = '';
17206 
17207     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
17208                                           $config['language']   = ICAL_LANG;
17209     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
17210     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
17211     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
17212     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
17213     $this->setConfig( $config );
17214 
17215   }
17216 /**
17217  * create formatted output for calendar component VJOURNAL object instance
17218  *
17219  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17220  * @since 2.5.1 - 2008-10-12
17221  * @param array $xcaldecl
17222  * @return string
17223  */
17224   function createComponent( &$xcaldecl ) {
17225     $objectname = $this->_createFormat();
17226     $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
17227     $component .= $this->createUid();
17228     $component .= $this->createDtstamp();
17229     $component .= $this->createAttach();
17230     $component .= $this->createAttendee();
17231     $component .= $this->createCategories();
17232     $component .= $this->createClass();
17233     $component .= $this->createComment();
17234     $component .= $this->createContact();
17235     $component .= $this->createCreated();
17236     $component .= $this->createDescription();
17237     $component .= $this->createDtstart();
17238     $component .= $this->createExdate();
17239     $component .= $this->createExrule();
17240     $component .= $this->createLastModified();
17241     $component .= $this->createOrganizer();
17242     $component .= $this->createRdate();
17243     $component .= $this->createRequestStatus();
17244     $component .= $this->createRecurrenceid();
17245     $component .= $this->createRelatedTo();
17246     $component .= $this->createRrule();
17247     $component .= $this->createSequence();
17248     $component .= $this->createStatus();
17249     $component .= $this->createSummary();
17250     $component .= $this->createUrl();
17251     $component .= $this->createXprop();
17252     $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
17253     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
17254       foreach( $this->xcaldecl as $localxcaldecl )
17255         $xcaldecl[] = $localxcaldecl;
17256     }
17257     return $component;
17258   }
17259 }
17260 /*********************************************************************************/
17261 /*********************************************************************************/
17262 /**
17263  * class for calendar component VFREEBUSY
17264  *
17265  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17266  * @since 2.5.1 - 2008-10-12
17267  */
17268 class vfreebusy extends calendarComponent {
17269   var $attendee;
17270   var $comment;
17271   var $contact;
17272   var $dtend;
17273   var $dtstart;
17274   var $duration;
17275   var $freebusy;
17276   var $organizer;
17277   var $requeststatus;
17278   var $url;
17279   var $xprop;
17280             //  component subcomponents container
17281   var $components;
17282 /**
17283  * constructor for calendar component VFREEBUSY object
17284  *
17285  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17286  * @since 2.8.2 - 2011-05-01
17287  * @param array $config
17288  * @return void
17289  */
17290   function vfreebusy( $config = array()) {
17291     $this->calendarComponent();
17292 
17293     $this->attendee        = '';
17294     $this->comment         = '';
17295     $this->contact         = '';
17296     $this->dtend           = '';
17297     $this->dtstart         = '';
17298     $this->duration        = '';
17299     $this->freebusy        = '';
17300     $this->organizer       = '';
17301     $this->requeststatus   = '';
17302     $this->url             = '';
17303     $this->xprop           = '';
17304 
17305     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
17306                                           $config['language']   = ICAL_LANG;
17307     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
17308     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
17309     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
17310     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
17311     $this->setConfig( $config );
17312 
17313   }
17314 /**
17315  * create formatted output for calendar component VFREEBUSY object instance
17316  *
17317  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17318  * @since 2.3.1 - 2007-11-19
17319  * @param array $xcaldecl
17320  * @return string
17321  */
17322   function createComponent( &$xcaldecl ) {
17323     $objectname = $this->_createFormat();
17324     $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
17325     $component .= $this->createUid();
17326     $component .= $this->createDtstamp();
17327     $component .= $this->createAttendee();
17328     $component .= $this->createComment();
17329     $component .= $this->createContact();
17330     $component .= $this->createDtstart();
17331     $component .= $this->createDtend();
17332     $component .= $this->createDuration();
17333     $component .= $this->createFreebusy();
17334     $component .= $this->createOrganizer();
17335     $component .= $this->createRequestStatus();
17336     $component .= $this->createUrl();
17337     $component .= $this->createXprop();
17338     $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
17339     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
17340       foreach( $this->xcaldecl as $localxcaldecl )
17341         $xcaldecl[] = $localxcaldecl;
17342     }
17343     return $component;
17344   }
17345 }
17346 /*********************************************************************************/
17347 /*********************************************************************************/
17348 /**
17349  * class for calendar component VALARM
17350  *
17351  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17352  * @since 2.5.1 - 2008-10-12
17353  */
17354 class valarm extends calendarComponent {
17355   var $action;
17356   var $attach;
17357   var $attendee;
17358   var $description;
17359   var $duration;
17360   var $repeat;
17361   var $summary;
17362   var $trigger;
17363   var $xprop;
17364 /**
17365  * constructor for calendar component VALARM object
17366  *
17367  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17368  * @since 2.8.2 - 2011-05-01
17369  * @param array $config
17370  * @return void
17371  */
17372   function valarm( $config = array()) {
17373     $this->calendarComponent();
17374 
17375     $this->action          = '';
17376     $this->attach          = '';
17377     $this->attendee        = '';
17378     $this->description     = '';
17379     $this->duration        = '';
17380     $this->repeat          = '';
17381     $this->summary         = '';
17382     $this->trigger         = '';
17383     $this->xprop           = '';
17384 
17385     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
17386                                           $config['language']   = ICAL_LANG;
17387     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
17388     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
17389     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
17390     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
17391     $this->setConfig( $config );
17392 
17393   }
17394 /**
17395  * create formatted output for calendar component VALARM object instance
17396  *
17397  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17398  * @since 2.5.1 - 2008-10-22
17399  * @param array $xcaldecl
17400  * @return string
17401  */
17402   function createComponent( &$xcaldecl ) {
17403     $objectname    = $this->_createFormat();
17404     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
17405     $component    .= $this->createAction();
17406     $component    .= $this->createAttach();
17407     $component    .= $this->createAttendee();
17408     $component    .= $this->createDescription();
17409     $component    .= $this->createDuration();
17410     $component    .= $this->createRepeat();
17411     $component    .= $this->createSummary();
17412     $component    .= $this->createTrigger();
17413     $component    .= $this->createXprop();
17414     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
17415     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
17416       foreach( $this->xcaldecl as $localxcaldecl )
17417         $xcaldecl[] = $localxcaldecl;
17418     }
17419     return $component;
17420   }
17421 }
17422 /**********************************************************************************
17423 /*********************************************************************************/
17424 /**
17425  * class for calendar component VTIMEZONE
17426  *
17427  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17428  * @since 2.5.1 - 2008-10-12
17429  */
17430 class vtimezone extends calendarComponent {
17431   var $timezonetype;
17432 
17433   var $comment;
17434   var $dtstart;
17435   var $lastmodified;
17436   var $rdate;
17437   var $rrule;
17438   var $tzid;
17439   var $tzname;
17440   var $tzoffsetfrom;
17441   var $tzoffsetto;
17442   var $tzurl;
17443   var $xprop;
17444             //  component subcomponents container
17445   var $components;
17446 /**
17447  * constructor for calendar component VTIMEZONE object
17448  *
17449  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17450  * @since 2.8.2 - 2011-05-01
17451  * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT )
17452  * @param array $config
17453  * @return void
17454  */
17455   function vtimezone( $timezonetype=FALSE, $config = array()) {
17456     if( is_array( $timezonetype )) {
17457       $config       = $timezonetype;
17458       $timezonetype = FALSE;
17459     }
17460     if( !$timezonetype )
17461       $this->timezonetype = 'VTIMEZONE';
17462     else
17463       $this->timezonetype = strtoupper( $timezonetype );
17464     $this->calendarComponent();
17465 
17466     $this->comment         = '';
17467     $this->dtstart         = '';
17468     $this->lastmodified    = '';
17469     $this->rdate           = '';
17470     $this->rrule           = '';
17471     $this->tzid            = '';
17472     $this->tzname          = '';
17473     $this->tzoffsetfrom    = '';
17474     $this->tzoffsetto      = '';
17475     $this->tzurl           = '';
17476     $this->xprop           = '';
17477 
17478     $this->components      = array();
17479 
17480     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
17481                                           $config['language']   = ICAL_LANG;
17482     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
17483     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
17484     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
17485     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
17486     $this->setConfig( $config );
17487 
17488   }
17489 /**
17490  * create formatted output for calendar component VTIMEZONE object instance
17491  *
17492  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17493  * @since 2.5.1 - 2008-10-25
17494  * @param array $xcaldecl
17495  * @return string
17496  */
17497   function createComponent( &$xcaldecl ) {
17498     $objectname    = $this->_createFormat();
17499     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
17500     $component    .= $this->createTzid();
17501     $component    .= $this->createLastModified();
17502     $component    .= $this->createTzurl();
17503     $component    .= $this->createDtstart();
17504     $component    .= $this->createTzoffsetfrom();
17505     $component    .= $this->createTzoffsetto();
17506     $component    .= $this->createComment();
17507     $component    .= $this->createRdate();
17508     $component    .= $this->createRrule();
17509     $component    .= $this->createTzname();
17510     $component    .= $this->createXprop();
17511     $component    .= $this->createSubComponent();
17512     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
17513     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
17514       foreach( $this->xcaldecl as $localxcaldecl )
17515         $xcaldecl[] = $localxcaldecl;
17516     }
17517     return $component;
17518   }
17519 }
17520 /*********************************************************************************/
17521 /*********************************************************************************/
17522 /**
17523  * moving all utility (static) functions to a utility class
17524  * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file
17525  *
17526  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17527  * @since 2.10.1 - 2011-07-16
17528  *
17529  */
17530 class iCalUtilityFunctions {
17531   // Store the single instance of iCalUtilityFunctions
17532   private static $m_pInstance;
17533 
17534   // Private constructor to limit object instantiation to within the class
17535   private function __construct() {
17536     $m_pInstance = FALSE;
17537   }
17538 
17539   // Getter method for creating/returning the single instance of this class
17540   public static function getInstance() {
17541     if (!self::$m_pInstance)
17542       self::$m_pInstance = new iCalUtilityFunctions();
17543 
17544     return self::$m_pInstance;
17545   }
17546 /**
17547  * ensures internal date-time/date format (keyed array) for an input date-time/date array (keyed or unkeyed)
17548  *
17549  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17550  * @since 2.14.1 - 2012-09-27
17551  * @param array $datetime
17552  * @param int $parno optional, default FALSE
17553  * @return array
17554  */
17555   public static function _date_time_array( $datetime, $parno=FALSE ) {
17556     return iCalUtilityFunctions::_chkDateArr( $datetime, $parno );
17557   }
17558   public static function _chkDateArr( $datetime, $parno=FALSE ) {
17559     $output = array();
17560     foreach( $datetime as $dateKey => $datePart ) {
17561       switch ( $dateKey ) {
17562         case '0': case 'year':   $output['year']  = $datePart; break;
17563         case '1': case 'month':  $output['month'] = $datePart; break;
17564         case '2': case 'day':    $output['day']   = $datePart; break;
17565       }
17566       if( 3 != $parno ) {
17567         switch ( $dateKey ) {
17568           case '0':
17569           case '1':
17570           case '2': break;
17571           case '3': case 'hour': $output['hour']  = $datePart; break;
17572           case '4': case 'min' : $output['min']   = $datePart; break;
17573           case '5': case 'sec' : $output['sec']   = $datePart; break;
17574           case '6': case 'tz'  : $output['tz']    = $datePart; break;
17575         }
17576       }
17577     }
17578     if( 3 != $parno ) {
17579       if( !isset( $output['hour'] ))         $output['hour'] = 0;
17580       if( !isset( $output['min']  ))         $output['min']  = 0;
17581       if( !isset( $output['sec']  ))         $output['sec']  = 0;
17582       if( isset( $output['tz'] ) &&
+17583         (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
17584                                              $output['tz']   = 'Z';
17585     }
17586     return $output;
17587   }
17588 /**
17589  * check date(-time) and params arrays for an opt. timezone and if it is a DATE-TIME or DATE (updates $parno and params)
17590  *
17591  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17592  * @since 2.10.30 - 2012-01-16
17593  * @param array $date, date to check
17594  * @param int $parno, no of date parts (i.e. year, month.. .)
17595  * @param array $params, property parameters
17596  * @return void
17597  */
17598   public static function _chkdatecfg( $theDate, & $parno, & $params ) {
17599     if( isset( $params['TZID'] ))
17600       $parno = 6;
17601     elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
17602       $parno = 3;
17603     else {
17604       if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
17605         $parno = 7;
17606       if( is_array( $theDate )) {
17607         if( isset( $theDate['timestamp'] ))
17608           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
17609         else
17610           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
17611         if( !empty( $tzid )) {
17612           $parno = 7;
17613           if( !iCalUtilityFunctions::_isOffset( $tzid ))
17614             $params['TZID'] = $tzid; // save only timezone
17615         }
17616         elseif( !$parno && ( 3 == count( $theDate )) &&
+17617           ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
17618           $parno = 3;
17619         else
17620           $parno = 6;
17621       }
17622       else { // string
17623         $date = trim( $theDate );
17624         if( 'Z' == substr( $date, -1 ))
17625           $parno = 7; // UTC DATE-TIME
17626         elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
+17627           ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
17628           $parno = 3; // DATE
17629         $date = iCalUtilityFunctions::_strdate2date( $date, $parno );
17630         unset( $date['unparsedtext'] );
17631         if( !empty( $date['tz'] )) {
17632           $parno = 7;
17633           if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
17634             $params['TZID'] = $date['tz']; // save only timezone
17635         }
17636         elseif( empty( $parno ))
17637           $parno = 6;
17638       }
17639       if( isset( $params['TZID'] ))
17640         $parno = 6;
17641     }
17642   }
17643 /**
17644  * vcalendar sort callback function
17645  *
17646  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17647  * @since 2.16.2 - 2012-12-17
17648  * @param array $a
17649  * @param array $b
17650  * @return int
17651  */
17652   public static function _cmpfcn( $a, $b ) {
17653     if(        empty( $a ))                       return -1;
17654     if(        empty( $b ))                       return  1;
17655     if( 'vtimezone' == $a->objName ) {
17656       if( 'vtimezone' != $b->objName )            return -1;
17657       elseif( $a->srtk[0] <= $b->srtk[0] )        return -1;
17658       else                                        return  1;
17659     }
17660     elseif( 'vtimezone' == $b->objName )          return  1;
17661     $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
17662     for( $k = 0; $k < 4 ; $k++ ) {
17663       if(        empty( $a->srtk[$k] ))           return -1;
17664       elseif(    empty( $b->srtk[$k] ))           return  1;
17665       if( is_array( $a->srtk[$k] )) {
17666         if( is_array( $b->srtk[$k] )) {
17667           foreach( $sortkeys as $key ) {
17668             if    ( !isset( $a->srtk[$k][$key] )) return -1;
17669             elseif( !isset( $b->srtk[$k][$key] )) return  1;
17670             if    (  empty( $a->srtk[$k][$key] )) return -1;
17671             elseif(  empty( $b->srtk[$k][$key] )) return  1;
17672             if    (         $a->srtk[$k][$key] == $b->srtk[$k][$key])
17673                                                   continue;
17674             if    ((  (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
17675                                                   return -1;
17676             elseif((  (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
17677                                                   return  1;
17678           }
17679         }
17680         else                                      return -1;
17681       }
17682       elseif( is_array( $b->srtk[$k] ))           return  1;
17683       elseif( $a->srtk[$k] < $b->srtk[$k] )       return -1;
17684       elseif( $a->srtk[$k] > $b->srtk[$k] )       return  1;
17685     }
17686     return 0;
17687   }
17688 /**
17689  * byte oriented line folding fix
17690  *
17691  * remove any line-endings that may include spaces or tabs
17692  * and convert all line endings (iCal default '\r\n'),
17693  * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
17694  *
17695  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17696  * @since 2.12.17 - 2012-07-12
17697  * @param string $text
17698  * @param string $nl
17699  * @return string
17700  */
17701   public static function convEolChar( & $text, $nl ) {
17702     $outp = '';
17703     $cix  = 0;
17704     while(    isset(   $text[$cix] )) {
17705       if(     isset(   $text[$cix + 2] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] ) &&
+17706         ((    " " ==   $text[$cix + 2] ) ||  ( "\t" == $text[$cix + 2] )))                    // 2 pos eolchar + ' ' or '\t'
17707         $cix  += 2;                                                                           // skip 3
17708       elseif( isset(   $text[$cix + 1] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] )) {
17709         $outp .= $nl;                                                                         // 2 pos eolchar
17710         $cix  += 1;                                                                           // replace with $nl
17711       }
17712       elseif( isset(   $text[$cix + 1] ) && (( "\r" == $text[$cix] ) || ( "\n" == $text[$cix] )) &&
+17713            (( " " ==   $text[$cix + 1] ) ||  ( "\t" == $text[$cix + 1] )))                     // 1 pos eolchar + ' ' or '\t'
17714         $cix  += 1;                                                                            // skip 2
17715       elseif(( "\r" == $text[$cix] )     ||  ( "\n" == $text[$cix] ))                          // 1 pos eolchar
17716         $outp .= $nl;                                                                          // replace with $nl
17717       else
17718         $outp .= $text[$cix];                                                                  // add any other byte
17719       $cix    += 1;
17720     }
17721     return $outp;
17722   }
17723 /**
17724  * create a calendar timezone and standard/daylight components
17725  *
17726  * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
17727  *
17728  * BEGIN:VTIMEZONE
17729  * TZID:Europe/Stockholm
17730  * BEGIN:STANDARD
17731  * DTSTART:20101031T020000
17732  * TZOFFSETFROM:+0200
17733  * TZOFFSETTO:+0100
17734  * TZNAME:CET
17735  * END:STANDARD
17736  * BEGIN:DAYLIGHT
17737  * DTSTART:20100328T030000
17738  * TZOFFSETFROM:+0100
17739  * TZOFFSETTO:+0200
17740  * TZNAME:CEST
17741  * END:DAYLIGHT
17742  * END:VTIMEZONE
17743  *
17744  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17745  * @since 2.16.1 - 2012-11-26
17746  * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
17747  * Additional changes jpirkey
17748  * @param object $calendar, reference to an iCalcreator calendar instance
17749  * @param string $timezone, a PHP5 (DateTimeZone) valid timezone
17750  * @param array  $xProp,    *[x-propName => x-propValue], optional
17751  * @param int    $from      a unix timestamp
17752  * @param int    $to        a unix timestamp
17753  * @return bool
17754  */
17755   public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
17756     if( empty( $timezone ))
17757       return FALSE;
17758     if( !empty( $from ) && !is_int( $from ))
17759       return FALSE;
17760     if( !empty( $to )   && !is_int( $to ))
17761       return FALSE;
17762     try {
17763       $dtz               = new DateTimeZone( $timezone );
17764       $transitions       = $dtz->getTransitions();
17765       $utcTz             = new DateTimeZone( 'UTC' );
17766     }
17767     catch( Exception $e ) { return FALSE; }
17768     if( empty( $to )) {
17769       $dates             = array_keys( $calendar->getProperty( 'dtstart' ));
17770       if( empty( $dates ))
17771         $dates           = array( date( 'Ymd' ));
17772     }
17773     if( !empty( $from ))
17774       $dateFrom          = new DateTime( "@$from" );             // set lowest date (UTC)
17775     else {
17776       $from              = reset( $dates );                      // set lowest date to the lowest dtstart date
17777       $dateFrom          = new DateTime( $from.'T000000', $dtz );
17778       $dateFrom->modify( '-1 month' );                           // set $dateFrom to one month before the lowest date
17779       $dateFrom->setTimezone( $utcTz );                          // convert local date to UTC
17780     }
17781     $dateFromYmd         = $dateFrom->format('Y-m-d' );
17782     if( !empty( $to ))
17783       $dateTo            = new DateTime( "@$to" );               // set end date (UTC)
17784     else {
17785       $to                = end( $dates );                        // set highest date to the highest dtstart date
17786       $dateTo            = new DateTime( $to.'T235959', $dtz );
17787       $dateTo->modify( '+1 year' );                              // set $dateTo to one year after the highest date
17788       $dateTo->setTimezone( $utcTz );                            // convert local date to UTC
17789     }
17790     $dateToYmd           = $dateTo->format('Y-m-d' );
17791     unset( $dtz );
17792     $transTemp           = array();
17793     $prevOffsetfrom      = 0;
17794     $stdIx  = $dlghtIx   = null;
17795     $prevTrans           = FALSE;
17796     foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!!
17797       $date              = new DateTime( "@{$trans['ts']}" );    // set transition date (UTC)
17798       $transDateYmd      = $date->format('Y-m-d' );
17799       if ( $transDateYmd < $dateFromYmd ) {
17800         $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom
17801         $prevTrans       = $trans;                               // save it in case we don't find any that match
17802         $prevTrans['offsetfrom'] = ( 0 < $tix ) ? $transitions[$tix-1]['offset'] : 0;
17803         continue;
17804       }
17805       if( $transDateYmd > $dateToYmd )
17806         break;                                                   // loop always (?) breaks here
17807       if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
17808         $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom
17809         $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date
17810         $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart and (opt) rdate setting
17811         $d = explode( '-', $d );
17812         $trans['time']   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
17813       }
17814       $prevOffsetfrom    = $trans['offset'];
17815       if( TRUE !== $trans['isdst'] ) {                           // standard timezone
17816         if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any repeating rdate's (in order)
+17817            ( $transTemp[$stdIx]['abbr']       ==   $trans['abbr'] )        &&
+17818            ( $transTemp[$stdIx]['offsetfrom'] ==   $trans['offsetfrom'] )  &&
+17819            ( $transTemp[$stdIx]['offset']     ==   $trans['offset'] )) {
17820           $transTemp[$stdIx]['rdate'][]        =   $trans['time'];
17821           continue;
17822         }
17823         $stdIx           = $tix;
17824       } // end standard timezone
17825       else {                                                     // daylight timezone
17826         if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any repeating rdate's (in order)
+17827            ( $transTemp[$dlghtIx]['abbr']       ==   $trans['abbr'] )         &&
+17828            ( $transTemp[$dlghtIx]['offsetfrom'] ==   $trans['offsetfrom'] )   &&
+17829            ( $transTemp[$dlghtIx]['offset']     ==   $trans['offset'] )) {
17830           $transTemp[$dlghtIx]['rdate'][]        =   $trans['time'];
17831           continue;
17832         }
17833         $dlghtIx         = $tix;
17834       } // end daylight timezone
17835       $transTemp[$tix]   = $trans;
17836     } // end foreach( $transitions as $tix => $trans )
17837     $tz  = & $calendar->newComponent( 'vtimezone' );
17838     $tz->setproperty( 'tzid', $timezone );
17839     if( !empty( $xProp )) {
17840       foreach( $xProp as $xPropName => $xPropValue )
17841         if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
17842           $tz->setproperty( $xPropName, $xPropValue );
17843     }
17844     if( empty( $transTemp )) {      // if no match found
17845       if( $prevTrans ) {            // then we use the last transition (before startdate) for the tz info
17846         $date = new DateTime( "@{$prevTrans['ts']}" );           // set transition date (UTC)
17847         $date->modify( $prevTrans['offsetfrom'].'seconds' );     // convert utc date to local date
17848         $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart setting
17849         $d = explode( '-', $d );
17850         $prevTrans['time'] = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
17851         $transTemp[0] = $prevTrans;
17852       }
17853       else {                        // or we use the timezone identifier to BUILD the standard tz info (?)
17854         $date = new DateTime( 'now', new DateTimeZone( $timezone ));
17855         $transTemp[0] = array( 'time'       => $date->format( 'Y-m-d\TH:i:s O' )
+17856                              , 'offset'     => $date->format( 'Z' )
+17857                              , 'offsetfrom' => $date->format( 'Z' )
+17858                              , 'isdst'      => FALSE );
17859       }
17860     }
17861     unset( $transitions, $date, $prevTrans );
17862     foreach( $transTemp as $tix => $trans ) {
17863       $type  = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
17864       $scomp = & $tz->newComponent( $type );
17865       $scomp->setProperty( 'dtstart',         $trans['time'] );
17866 //      $scomp->setProperty( 'x-utc-timestamp', $tix.' : '.$trans['ts'] );   // test ###
17867       if( !empty( $trans['abbr'] ))
17868         $scomp->setProperty( 'tzname',        $trans['abbr'] );
17869       if( isset( $trans['offsetfrom'] ))
17870         $scomp->setProperty( 'tzoffsetfrom',  iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
17871       $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
17872       if( isset( $trans['rdate'] ))
17873         $scomp->setProperty( 'RDATE',         $trans['rdate'] );
17874     }
17875     return TRUE;
17876   }
17877 /**
17878  * creates formatted output for calendar component property data value type date/date-time
17879  *
17880  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17881  * @since 2.14.1 - 2012-09-17
17882  * @param array   $datetime
17883  * @param int     $parno, optional, default 6
17884  * @return string
17885  */
17886   public static function _format_date_time( $datetime, $parno=6 ) {
17887     return iCalUtilityFunctions::_date2strdate( $datetime, $parno );
17888   }
17889   public static function _date2strdate( $datetime, $parno=6 ) {
17890     if( !isset( $datetime['year'] )  &&
17891         !isset( $datetime['month'] ) &&
17892         !isset( $datetime['day'] )   &&
17893         !isset( $datetime['hour'] )  &&
17894         !isset( $datetime['min'] )   &&
17895         !isset( $datetime['sec'] ))
17896       return;
17897     $output     = null;
17898     foreach( $datetime as $dkey => & $dvalue )
17899       if( 'tz' != $dkey ) $dvalue = (integer) $dvalue;
17900     $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] );
17901     if( 3 == $parno )
17902       return $output;
17903     if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
17904     if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
17905     if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
17906     $output    .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] );
17907     if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
17908       $datetime['tz'] = trim( $datetime['tz'] );
17909       if( 'Z'  == $datetime['tz'] )
17910         $parno  = 7;
17911       elseif( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
17912         $parno  = 7;
17913         $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
17914         try {
17915           $d    = new DateTime( $output, new DateTimeZone( 'UTC' ));
17916           if( 0 != $offset ) // adjust för offset
17917             $d->modify( "$offset seconds" );
17918           $output = $d->format( 'Ymd\THis' );
17919         }
17920         catch( Exception $e ) {
17921           $output = date( 'Ymd\THis', mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year'] ));
17922         }
17923       }
17924       if( 7 == $parno )
17925         $output .= 'Z';
17926     }
17927     return $output;
17928   }
17929 /**
17930  * convert a date/datetime (array) to timestamp
17931  *
17932  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17933  * @since 2.14.1 - 2012-09-29
17934  * @param array  $datetime  datetime(/date)
17935  * @param string $wtz       timezone
17936  * @return int
17937  */
17938   public static function _date2timestamp( $datetime, $wtz=null ) {
17939     if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
17940     if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
17941     if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
17942     if( empty( $wtz ) && ( !isset( $datetime['tz'] ) || empty(  $datetime['tz'] )))
17943       return mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
17944     $output = $offset = 0;
17945     if( empty( $wtz )) {
17946       if( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
17947         $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) * -1;
17948         $wtz    = 'UTC';
17949       }
17950       else
17951         $wtz    = $datetime['tz'];
17952     }
17953     if(( 'Z' == $wtz ) || ( 'GMT' == strtoupper( $wtz )))
17954       $wtz      = 'UTC';
17955     try {
17956       $strdate  = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['min'], $datetime['sec'] );
17957       $d        = new DateTime( $strdate, new DateTimeZone( $wtz ));
17958       if( 0    != $offset )  // adjust for offset
17959         $d->modify( $offset.' seconds' );
17960       $output   = $d->format( 'U' );
17961       unset( $d );
17962     }
17963     catch( Exception $e ) {
17964       $output = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
17965     }
17966     return $output;
17967   }
17968 /**
17969  * ensures internal duration format for input in array format
17970  *
17971  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
17972  * @since 2.14.1 - 2012-09-25
17973  * @param array $duration
17974  * @return array
17975  */
17976   public static function _duration_array( $duration ) {
17977     return iCalUtilityFunctions::_duration2arr( $duration );
17978   }
17979   public static function _duration2arr( $duration ) {
17980     $output = array();
17981     if(    is_array( $duration )        &&
+17982        ( 1 == count( $duration ))       &&
17983               isset( $duration['sec'] ) &&
+17984               ( 60 < $duration['sec'] )) {
17985       $durseconds  = $duration['sec'];
17986       $output['week'] = (int) floor( $durseconds / ( 60 * 60 * 24 * 7 ));
17987       $durseconds     =              $durseconds % ( 60 * 60 * 24 * 7 );
17988       $output['day']  = (int) floor( $durseconds / ( 60 * 60 * 24 ));
17989       $durseconds     =              $durseconds % ( 60 * 60 * 24 );
17990       $output['hour'] = (int) floor( $durseconds / ( 60 * 60 ));
17991       $durseconds     =              $durseconds % ( 60 * 60 );
17992       $output['min']  = (int) floor( $durseconds / ( 60 ));
17993       $output['sec']  =            ( $durseconds % ( 60 ));
17994     }
17995     else {
17996       foreach( $duration as $durKey => $durValue ) {
17997         if( empty( $durValue )) continue;
17998         switch ( $durKey ) {
17999           case '0': case 'week': $output['week']  = $durValue; break;
18000           case '1': case 'day':  $output['day']   = $durValue; break;
18001           case '2': case 'hour': $output['hour']  = $durValue; break;
18002           case '3': case 'min':  $output['min']   = $durValue; break;
18003           case '4': case 'sec':  $output['sec']   = $durValue; break;
18004         }
18005       }
18006     }
18007     if( isset( $output['week'] ) && ( 0 < $output['week'] )) {
18008       unset( $output['day'], $output['hour'], $output['min'], $output['sec'] );
18009       return $output;
18010     }
18011     unset( $output['week'] );
18012     if( empty( $output['day'] ))
18013       unset( $output['day'] );
18014     if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) {
18015       if( !isset( $output['hour'] )) $output['hour'] = 0;
18016       if( !isset( $output['min']  )) $output['min']  = 0;
18017       if( !isset( $output['sec']  )) $output['sec']  = 0;
18018       if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
18019         unset( $output['hour'], $output['min'], $output['sec'] );
18020     }
18021     return $output;
18022   }
18023 /**
18024  * convert startdate+duration to a array format datetime
18025  *
18026  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18027  * @since 2.15.12 - 2012-10-31
18028  * @param array   $startdate
18029  * @param array   $duration
18030  * @return array, date format
18031  */
18032   public static function _duration2date( $startdate, $duration ) {
18033     $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
18034     $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
18035     $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0;
18036     $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0;
18037     $dtend = 0;
18038     if(    isset( $duration['week'] )) $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
18039     if(    isset( $duration['day'] ))  $dtend += ( $duration['day'] * 24 * 60 * 60 );
18040     if(    isset( $duration['hour'] )) $dtend += ( $duration['hour'] * 60 *60 );
18041     if(    isset( $duration['min'] ))  $dtend += ( $duration['min'] * 60 );
18042     if(    isset( $duration['sec'] ))  $dtend +=   $duration['sec'];
18043     $date     = date( 'Y-m-d-H-i-s', mktime((int) $startdate['hour'], (int) $startdate['min'], (int) ( $startdate['sec'] + $dtend ), (int) $startdate['month'], (int) $startdate['day'], (int) $startdate['year'] ));
18044     $d        = explode( '-', $date );
18045     $dtend2   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
18046     if( isset( $startdate['tz'] ))
18047       $dtend2['tz']   = $startdate['tz'];
18048     if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
18049       unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
18050     return $dtend2;
18051   }
18052 /**
18053  * ensures internal duration format for an input string (iCal) formatted duration
18054  *
18055  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18056  * @since 2.14.1 - 2012-09-25
18057  * @param string $duration
18058  * @return array
18059  */
18060   public static function _duration_string( $duration ) {
18061     return iCalUtilityFunctions::_durationStr2arr( $duration );
18062   }
18063   public static function _durationStr2arr( $duration ) {
18064     $duration = (string) trim( $duration );
18065     while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
18066       if( 0 < strlen( $duration ))
18067         $duration = substr( $duration, 1 );
18068       else
18069         return false; // no leading P !?!?
18070     }
18071     $duration = substr( $duration, 1 ); // skip P
18072     $duration = str_replace ( 't', 'T', $duration );
18073     $duration = str_replace ( 'T', '', $duration );
18074     $output = array();
18075     $val    = null;
18076     for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
18077       switch( strtoupper( substr( $duration, $ix, 1 ))) {
18078        case 'W':
18079          $output['week'] = $val;
18080          $val            = null;
18081          break;
18082        case 'D':
18083          $output['day']  = $val;
18084          $val            = null;
18085          break;
18086        case 'H':
18087          $output['hour'] = $val;
18088          $val            = null;
18089          break;
18090        case 'M':
18091          $output['min']  = $val;
18092          $val            = null;
18093          break;
18094        case 'S':
18095          $output['sec']  = $val;
18096          $val            = null;
18097          break;
18098        default:
18099          if( !ctype_digit( substr( $duration, $ix, 1 )))
18100            return false; // unknown duration control character  !?!?
18101          else
18102            $val .= substr( $duration, $ix, 1 );
18103       }
18104     }
18105     return iCalUtilityFunctions::_duration2arr( $output );
18106   }
18107 /**
18108  * creates formatted output for calendar component property data value type duration
18109  *
18110  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18111  * @since 2.15.8 - 2012-10-30
18112  * @param array $duration, array( week, day, hour, min, sec )
18113  * @return string
18114  */
18115   public static function _format_duration( $duration ) {
18116     return iCalUtilityFunctions::_duration2str( $duration );
18117   }
18118   public static function _duration2str( $duration ) {
18119     if( isset( $duration['week'] ) ||
18120         isset( $duration['day'] )  ||
18121         isset( $duration['hour'] ) ||
18122         isset( $duration['min'] )  ||
18123         isset( $duration['sec'] ))
18124        $ok = TRUE;
18125     else
18126       return;
18127     if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
18128       return 'P'.$duration['week'].'W';
18129     $output = 'P';
18130     if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
18131       $output .= $duration['day'].'D';
18132     if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
+18133        ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ||
+18134        ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))) {
18135       $output .= 'T';
18136       $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H';
18137       $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '0M';
18138       $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '0S';
18139     }
18140     if( 'P' == $output )
18141       $output = 'PT0H0M0S';
18142     return $output;
18143   }
18144 /**
18145  * removes expkey+expvalue from array and returns hitval (if found) else returns elseval
18146  *
18147  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18148  * @since 2.4.16 - 2008-11-08
18149  * @param array $array
18150  * @param string $expkey, expected key
18151  * @param string $expval, expected value
18152  * @param int $hitVal optional, return value if found
18153  * @param int $elseVal optional, return value if not found
18154  * @param int $preSet optional, return value if already preset
18155  * @return int
18156  */
18157   public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
18158     if( $preSet )
18159       return $preSet;
18160     if( !is_array( $array ) || ( 0 == count( $array )))
18161       return $elseVal;
18162     foreach( $array as $key => $value ) {
18163       if( strtoupper( $expkey ) == strtoupper( $key )) {
18164         if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
18165           unset( $array[$key] );
18166           return $hitVal;
18167         }
18168       }
18169     }
18170     return $elseVal;
18171   }
18172 /**
18173  * checks if input contains a (array formatted) date/time
18174  *
18175  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18176  * @since 2.11.8 - 2012-01-20
18177  * @param array $input
18178  * @return bool
18179  */
18180   public static function _isArrayDate( $input ) {
18181     if( !is_array( $input ))
18182       return FALSE;
18183     if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 ))))
18184       return FALSE;
18185     if( 7 == count( $input ))
18186       return TRUE;
18187     if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
18188       return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
18189     if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
18190       return FALSE;
18191     if( in_array( 0, $input ))
18192       return FALSE;
18193     if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
18194       return FALSE;
18195     if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
+18196          checkdate( (int) $input[1], (int) $input[2], (int) $input[0] ))
18197       return TRUE;
18198     $input = iCalUtilityFunctions::_strdate2date( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y
18199     if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
18200       return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
18201     return FALSE;
18202   }
18203 /**
18204  * checks if input array contains a timestamp date
18205  *
18206  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18207  * @since 2.4.16 - 2008-10-18
18208  * @param array $input
18209  * @return bool
18210  */
18211   public static function _isArrayTimestampDate( $input ) {
18212     return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
18213   }
18214 /**
18215  * controls if input string contains (trailing) UTC/iCal offset
18216  *
18217  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18218  * @since 2.14.1 - 2012-09-21
18219  * @param string $input
18220  * @return bool
18221  */
18222   public static function _isOffset( $input ) {
18223     $input         = trim( (string) $input );
18224     if( 'Z' == substr( $input, -1 ))
18225       return TRUE;
18226     elseif((   5 <= strlen( $input )) &&
+18227        ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
+18228        (   '0000' <= substr( $input, -4 )) && (   '9999' >= substr( $input, -4 )))
18229       return TRUE;
18230     elseif((    7 <= strlen( $input )) &&
+18231        ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
+18232        ( '000000' <= substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
18233       return TRUE;
18234     return FALSE;
18235   }
18236 /**
18237  * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
18238  * matching (MS) UCT offset and time zone descriptors
18239  *
18240  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18241  * @since 2.14.1 - 2012-09-16
18242  * @param string $timezone, input/output variable reference
18243  * @return bool
18244  */
18245   public static function ms2phpTZ( & $timezone ) {
18246     if( empty( $timezone ))
18247       return FALSE;
18248     $search = str_replace( '"', '', $timezone );
18249     $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
18250     if( '(UTC' != substr( $search, 0, 4 ))
18251       return FALSE;
18252     if( FALSE === ( $pos = strpos( $search, ')' )))
18253       return FALSE;
18254     $pos    = strpos( $search, ')' );
18255     $searchOffset = substr( $search, 4, ( $pos - 4 ));
18256     $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
18257     while( ' ' ==substr( $search, ( $pos + 1 )))
18258       $pos += 1;
18259     $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) ));
18260     $searchWords  = explode( ' ', $searchText );
18261     $timezone_abbreviations = DateTimeZone::listAbbreviations();
18262     $hits = array();
18263     foreach( $timezone_abbreviations as $name => $transitions ) {
18264       foreach( $transitions as $cnt => $transition ) {
18265         if( empty( $transition['offset'] )      ||
18266             empty( $transition['timezone_id'] ) ||
+18267           ( $transition['offset'] != $searchOffset ))
18268         continue;
18269         $cWords = explode( '/', $transition['timezone_id'] );
18270         $cPrio   = $hitCnt = $rank = 0;
18271         foreach( $cWords as $cWord ) {
18272           if( empty( $cWord ))
18273             continue;
18274           $cPrio += 1;
18275           $sPrio  = 0;
18276           foreach( $searchWords as $sWord ) {
18277             if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
18278               continue;
18279             $sPrio += 1;
18280             if( strtolower( $cWord ) == strtolower( $sWord )) {
18281               $hitCnt += 1;
18282               $rank   += ( $cPrio + $sPrio );
18283             }
18284             else
18285               $rank += 10;
18286           }
18287         }
18288         if( 0 < $hitCnt ) {
18289           $hits[$rank][] = $transition['timezone_id'];
18290         }
18291       }
18292     }
18293     unset( $timezone_abbreviations );
18294     if( empty( $hits ))
18295       return FALSE;
18296     ksort( $hits );
18297     foreach( $hits as $rank => $tzs ) {
18298       if( !empty( $tzs )) {
18299         $timezone = reset( $tzs );
18300         return TRUE;
18301       }
18302     }
18303     return FALSE;
18304   }
18305 /**
18306  * transforms offset in seconds to [-/+]hhmm[ss]
18307  *
18308  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18309  * @since 2011-05-02
18310  * @param string $seconds
18311  * @return string
18312  */
18313   public static function offsetSec2His( $seconds ) {
18314     if( '-' == substr( $seconds, 0, 1 )) {
18315       $prefix  = '-';
18316       $seconds = substr( $seconds, 1 );
18317     }
18318     elseif( '+' == substr( $seconds, 0, 1 )) {
18319       $prefix  = '+';
18320       $seconds = substr( $seconds, 1 );
18321     }
18322     else
18323       $prefix  = '+';
18324     $output  = '';
18325     $hour    = (int) floor( $seconds / 3600 );
18326     if( 10 > $hour )
18327       $hour  = '0'.$hour;
18328     $seconds = $seconds % 3600;
18329     $min     = (int) floor( $seconds / 60 );
18330     if( 10 > $min )
18331       $min   = '0'.$min;
18332     $output  = $hour.$min;
18333     $seconds = $seconds % 60;
18334     if( 0 < $seconds) {
18335       if( 9 < $seconds)
18336         $output .= $seconds;
18337       else
18338         $output .= '0'.$seconds;
18339     }
18340     return $prefix.$output;
18341   }
18342 /**
18343  * updates an array with dates based on a recur pattern
18344  *
18345  * if missing, UNTIL is set 1 year from startdate (emergency break)
18346  *
18347  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18348  * @since 2.10.19 - 2011-10-31
18349  * @param array $result, array to update, array([timestamp] => timestamp)
18350  * @param array $recur, pattern for recurrency (only value part, params ignored)
18351  * @param array $wdate, component start date
18352  * @param array $startdate, start date
18353  * @param array $enddate, optional
18354  * @return void
18355  * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start
18356  */
18357   public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) {
18358     foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
18359     $wdateStart  = $wdate;
18360     $wdatets     = iCalUtilityFunctions::_date2timestamp( $wdate );
18361     $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate );
18362     if( !$enddate ) {
18363       $enddate = $startdate;
18364       $enddate['year'] += 1;
18365     }
18366 // echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br />\n";print_r($recur);echo "<br />\n";//test###
18367     $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break
18368     if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
18369       $recur['UNTIL'] = $enddate; // create break
18370     if( isset( $recur['UNTIL'] )) {
18371       $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] );
18372       if( $endDatets > $tdatets ) {
18373         $endDatets = $tdatets; // emergency break
18374         $enddate   = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
18375       }
18376       else
18377         $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
18378     }
18379     if( $wdatets > $endDatets ) {
18380 // echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
18381       return array(); // nothing to do.. .
18382     }
18383     if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
18384       $recur['FREQ'] = 'DAILY'; // ??
18385     $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
18386     $weekStart = (int) date( 'W', ( $wdatets + $wkst ));
18387     if( !isset( $recur['INTERVAL'] ))
18388       $recur['INTERVAL'] = 1;
18389     $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
18390             /* find out how to step up dates and set index for interval count */
18391     $step = array();
18392     if( 'YEARLY' == $recur['FREQ'] )
18393       $step['year']  = 1;
18394     elseif( 'MONTHLY' == $recur['FREQ'] )
18395       $step['month'] = 1;
18396     elseif( 'WEEKLY' == $recur['FREQ'] )
18397       $step['day']   = 7;
18398     else
18399       $step['day']   = 1;
18400     if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
18401       $step = array( 'month' => 1 );
18402     if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
18403       $step = array( 'day' => 7 );
18404     if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
18405       $step = array( 'day' => 1 );
18406     $intervalarr = array();
18407     if( 1 < $recur['INTERVAL'] ) {
18408       $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
18409       $intervalarr = array( $intervalix => 0 );
18410     }
18411     if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
18412       $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
18413 // echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br />\n"; // test ###
18414       if( is_array( $recur['BYSETPOS'] )) {
18415         foreach( $recur['BYSETPOS'] as $bix => $bval )
18416           $recur['BYSETPOS'][$bix] = (int) $bval;
18417       }
18418       else
18419         $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
18420       if( 'YEARLY' == $recur['FREQ'] ) {
18421         $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
18422         $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
18423         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year
18424       }
18425       elseif( 'MONTHLY' == $recur['FREQ'] ) {
18426         $wdate['day']   = 1; // start from beginning of month
18427         $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
18428         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month
18429       }
18430       else
18431         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period
18432 // echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br />\n";//test###
18433       $bysetposWold = (int) date( 'W', ( $wdatets + $wkst ));
18434       $bysetposYold = $wdate['year'];
18435       $bysetposMold = $wdate['month'];
18436       $bysetposDold = $wdate['day'];
18437     }
18438     else
18439       iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
18440     $year_old     = null;
18441     $daynames     = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
18442              /* MAIN LOOP */
18443 // echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br />\n";//test
18444     while( TRUE ) {
18445       if( isset( $endDatets ) && ( $wdatets > $endDatets ))
18446         break;
18447       if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
18448         break;
18449       if( $year_old != $wdate['year'] ) {
18450         $year_old   = $wdate['year'];
18451         $daycnts    = array();
18452         $yeardays   = $weekno = 0;
18453         $yeardaycnt = array();
18454         foreach( $daynames as $dn )
18455           $yeardaycnt[$dn] = 0;
18456         for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
18457           $daycnts[$m] = array();
18458           $weekdaycnt = array();
18459           foreach( $daynames as $dn )
18460             $weekdaycnt[$dn] = 0;
18461           $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
18462           for( $d   = 1; $d <= $mcnt; $d++ ) {
18463             $daycnts[$m][$d] = array();
18464             if( isset( $recur['BYYEARDAY'] )) {
18465               $yeardays++;
18466               $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
18467             }
18468             if( isset( $recur['BYDAY'] )) {
18469               $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
18470               $day    = $daynames[$day];
18471               $daycnts[$m][$d]['DAY'] = $day;
18472               $weekdaycnt[$day]++;
18473               $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
18474               $yeardaycnt[$day]++;
18475               $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
18476             }
18477             if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
18478               $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
18479           }
18480         }
18481         $daycnt = 0;
18482         $yeardaycnt = array();
18483         if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
18484           $weekno = null;
18485           for( $d=31; $d > 25; $d-- ) { // get last weekno for year
18486             if( !$weekno )
18487               $weekno = $daycnts[12][$d]['weekno_up'];
18488             elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
18489               $weekno = $daycnts[12][$d]['weekno_up'];
18490               break;
18491             }
18492           }
18493         }
18494         for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
18495           $weekdaycnt = array();
18496           foreach( $daynames as $dn )
18497             $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
18498           $monthcnt = 0;
18499           $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
18500           for( $d   = $mcnt; $d > 0; $d-- ) {
18501             if( isset( $recur['BYYEARDAY'] )) {
18502               $daycnt -= 1;
18503               $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
18504             }
18505             if( isset( $recur['BYMONTHDAY'] )) {
18506               $monthcnt -= 1;
18507               $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
18508             }
18509             if( isset( $recur['BYDAY'] )) {
18510               $day  = $daycnts[$m][$d]['DAY'];
18511               $weekdaycnt[$day] -= 1;
18512               $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
18513               $yeardaycnt[$day] -= 1;
18514               $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
18515             }
18516             if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
18517               $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
18518           }
18519         }
18520       }
18521             /* check interval */
18522       if( 1 < $recur['INTERVAL'] ) {
18523             /* create interval index */
18524         $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
18525             /* check interval */
18526         $currentKey = array_keys( $intervalarr );
18527         $currentKey = end( $currentKey ); // get last index
18528         if( $currentKey != $intervalix )
18529           $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
18530         if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
+18531            ( 0 != $intervalarr[$intervalix] )) {
18532             /* step up date */
18533 // echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
18534           iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
18535           continue;
18536         }
18537         else // continue within the selected interval
18538           $intervalarr[$intervalix] = 0;
18539 // echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
18540       }
18541       $updateOK = TRUE;
18542       if( $updateOK && isset( $recur['BYMONTH'] ))
18543         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
+18544                                            , $wdate['month']
+18545                                            ,($wdate['month'] - 13));
18546       if( $updateOK && isset( $recur['BYWEEKNO'] ))
18547         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
+18548                                            , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
+18549                                            , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
18550       if( $updateOK && isset( $recur['BYYEARDAY'] ))
18551         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
+18552                                            , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
+18553                                            , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
18554       if( $updateOK && isset( $recur['BYMONTHDAY'] ))
18555         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
+18556                                            , $wdate['day']
+18557                                            , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
18558 // echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n";//test###
18559       if( $updateOK && isset( $recur['BYDAY'] )) {
18560         $updateOK = FALSE;
18561         $m = $wdate['month'];
18562         $d = $wdate['day'];
18563         if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
18564           $daynoexists = $daynosw = $daynamesw =  FALSE;
18565           if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
18566             $daynamesw = TRUE;
18567           if( isset( $recur['BYDAY'][0] )) {
18568             $daynoexists = TRUE;
18569             if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
18570               $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
+18571                                                 , $daycnts[$m][$d]['monthdayno_up']
+18572                                                 , $daycnts[$m][$d]['monthdayno_down'] );
18573             elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
18574               $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
+18575                                                 , $daycnts[$m][$d]['yeardayno_up']
+18576                                                 , $daycnts[$m][$d]['yeardayno_down'] );
18577           }
18578           if((  $daynoexists &&  $daynosw && $daynamesw ) ||
+18579              ( !$daynoexists && !$daynosw && $daynamesw )) {
18580             $updateOK = TRUE;
18581 // echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
18582           }
18583 // echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
18584         }
18585         else {
18586           foreach( $recur['BYDAY'] as $bydayvalue ) {
18587             $daynoexists = $daynosw = $daynamesw = FALSE;
18588             if( isset( $bydayvalue['DAY'] ) &&
+18589                      ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
18590               $daynamesw = TRUE;
18591             if( isset( $bydayvalue[0] )) {
18592               $daynoexists = TRUE;
18593               if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
18594                    isset( $recur['BYMONTH'] ))
18595                 $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
+18596                                                   , $daycnts[$m][$d]['monthdayno_up']
+18597                                                   , $daycnts[$m][$d]['monthdayno_down'] );
18598               elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
18599                 $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
+18600                                                   , $daycnts[$m][$d]['yeardayno_up']
+18601                                                   , $daycnts[$m][$d]['yeardayno_down'] );
18602             }
18603 // echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br />\n"; // test ###
18604             if((  $daynoexists &&  $daynosw && $daynamesw ) ||
+18605                ( !$daynoexists && !$daynosw && $daynamesw )) {
18606               $updateOK = TRUE;
18607               break;
18608             }
18609           }
18610         }
18611       }
18612 // echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n"; // test ###
18613             /* check BYSETPOS */
18614       if( $updateOK ) {
18615         if( isset( $recur['BYSETPOS'] ) &&
+18616           ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
18617           if( isset( $recur['WEEKLY'] )) {
18618             if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
18619               $bysetposw1[] = $wdatets;
18620             else
18621               $bysetposw2[] = $wdatets;
18622           }
18623           else {
18624             if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  &&
+18625                                             ( $bysetposYold == $wdate['year'] ))   ||
+18626                ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  &&
+18627                                            (( $bysetposYold == $wdate['year'] )  &&
+18628                                             ( $bysetposMold == $wdate['month'] ))) ||
+18629                ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  &&
+18630                                            (( $bysetposYold == $wdate['year'] )  &&
+18631                                             ( $bysetposMold == $wdate['month'])  &&
+18632                                             ( $bysetposDold == $wdate['day'] )))) {
18633 // echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
18634               $bysetposymd1[] = $wdatets;
18635             }
18636             else {
18637 // echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
18638               $bysetposymd2[] = $wdatets;
18639             }
18640           }
18641         }
18642         else {
18643             /* update result array if BYSETPOS is set */
18644           $countcnt++;
18645           if( $startdatets <= $wdatets ) { // only output within period
18646             $result[$wdatets] = TRUE;
18647 // echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
18648           }
18649 // echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br />\n";//test
18650           $updateOK = FALSE;
18651         }
18652       }
18653             /* step up date */
18654       iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
18655             /* check if BYSETPOS is set for updating result array */
18656       if( $updateOK && isset( $recur['BYSETPOS'] )) {
18657         $bysetpos       = FALSE;
18658         if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) &&
+18659           ( $bysetposYold != $wdate['year'] )) {
18660           $bysetpos     = TRUE;
18661           $bysetposYold = $wdate['year'];
18662         }
18663         elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
+18664          (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
18665           $bysetpos     = TRUE;
18666           $bysetposYold = $wdate['year'];
18667           $bysetposMold = $wdate['month'];
18668         }
18669         elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) {
18670           $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
18671           if( $bysetposWold != $weekno ) {
18672             $bysetposWold = $weekno;
18673             $bysetpos     = TRUE;
18674           }
18675         }
18676         elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) &&
+18677          (( $bysetposYold != $wdate['year'] )  ||
+18678           ( $bysetposMold != $wdate['month'] ) ||
+18679           ( $bysetposDold != $wdate['day'] ))) {
18680           $bysetpos     = TRUE;
18681           $bysetposYold = $wdate['year'];
18682           $bysetposMold = $wdate['month'];
18683           $bysetposDold = $wdate['day'];
18684         }
18685         if( $bysetpos ) {
18686           if( isset( $recur['BYWEEKNO'] )) {
18687             $bysetposarr1 = & $bysetposw1;
18688             $bysetposarr2 = & $bysetposw2;
18689           }
18690           else {
18691             $bysetposarr1 = & $bysetposymd1;
18692             $bysetposarr2 = & $bysetposymd2;
18693           }
18694 // echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ###
18695           foreach( $recur['BYSETPOS'] as $ix ) {
18696             if( 0 > $ix ) // both positive and negative BYSETPOS allowed
18697               $ix = ( count( $bysetposarr1 ) + $ix + 1);
18698             $ix--;
18699             if( isset( $bysetposarr1[$ix] )) {
18700               if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period
18701 //                $testdate   = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 );                // test ###
18702 //                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ###
18703 // echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)";   // test ###
18704                 $result[$bysetposarr1[$ix]] = TRUE;
18705 // echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ###
18706               }
18707               $countcnt++;
18708             }
18709             if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
18710               break;
18711           }
18712 // echo "<br />\n"; // test ###
18713           $bysetposarr1 = $bysetposarr2;
18714           $bysetposarr2 = array();
18715         }
18716       }
18717     }
18718   }
18719   public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
18720     if( is_array( $BYvalue ) &&
+18721       ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
18722       return TRUE;
18723     elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
18724       return TRUE;
18725     else
18726       return FALSE;
18727   }
18728   public static function _recurIntervalIx( $freq, $date, $wkst ) {
18729             /* create interval index */
18730     switch( $freq ) {
18731       case 'YEARLY':
18732         $intervalix = $date['year'];
18733         break;
18734       case 'MONTHLY':
18735         $intervalix = $date['year'].'-'.$date['month'];
18736         break;
18737       case 'WEEKLY':
18738         $wdatets    = iCalUtilityFunctions::_date2timestamp( $date );
18739         $intervalix = (int) date( 'W', ( $wdatets + $wkst ));
18740        break;
18741       case 'DAILY':
18742            default:
18743         $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
18744         break;
18745     }
18746     return $intervalix;
18747   }
18748 /**
18749  * convert input format for exrule and rrule to internal format
18750  *
18751  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18752  * @since 2.14.1 - 2012-09-24
18753  * @param array $rexrule
18754  * @return array
18755  */
18756   public static function _setRexrule( $rexrule ) {
18757     $input          = array();
18758     if( empty( $rexrule ))
18759       return $input;
18760     foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
18761       $rexrulelabel = strtoupper( $rexrulelabel );
18762       if( 'UNTIL'  != $rexrulelabel )
18763         $input[$rexrulelabel]   = $rexrulevalue;
18764       else {
18765         iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
18766         if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time UTC
18767           $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 7, 'UTC' );
18768         elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or UTC date-time
18769           $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 7 : 3;
18770           $d = iCalUtilityFunctions::_chkDateArr( $rexrulevalue, $parno );
18771           if(( 3 < $parno ) && isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
18772             $strdate              = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
18773             $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
18774             unset( $input[$rexrulelabel]['unparsedtext'] );
18775           }
18776           else
18777            $input[$rexrulelabel] = $d;
18778         }
18779         elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC
18780           $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $rexrulevalue );
18781           unset( $input['$rexrulelabel']['unparsedtext'] );
18782         }
18783         if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
18784           $input[$rexrulelabel]['tz'] = 'Z';
18785       }
18786     }
18787             /* set recurrence rule specification in rfc2445 order */
18788     $input2 = array();
18789     if( isset( $input['FREQ'] ))
18790       $input2['FREQ']       = $input['FREQ'];
18791     if( isset( $input['UNTIL'] ))
18792       $input2['UNTIL']      = $input['UNTIL'];
18793     elseif( isset( $input['COUNT'] ))
18794       $input2['COUNT']      = $input['COUNT'];
18795     if( isset( $input['INTERVAL'] ))
18796       $input2['INTERVAL']   = $input['INTERVAL'];
18797     if( isset( $input['BYSECOND'] ))
18798       $input2['BYSECOND']   = $input['BYSECOND'];
18799     if( isset( $input['BYMINUTE'] ))
18800       $input2['BYMINUTE']   = $input['BYMINUTE'];
18801     if( isset( $input['BYHOUR'] ))
18802       $input2['BYHOUR']     = $input['BYHOUR'];
18803     if( isset( $input['BYDAY'] )) {
18804       if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
18805         $input2['BYDAY']    = strtoupper( $input['BYDAY'] );
18806       else {
18807         foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
18808           if( 'DAY'        == strtoupper( $BYDAYx ))
18809              $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
18810           elseif( !is_array( $BYDAYv )) {
18811              $input2['BYDAY'][$BYDAYx]  = $BYDAYv;
18812           }
18813           else {
18814             foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
18815               if( 'DAY'    == strtoupper( $BYDAYx2 ))
18816                  $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
18817               else
18818                  $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
18819             }
18820           }
18821         }
18822       }
18823     }
18824     if( isset( $input['BYMONTHDAY'] ))
18825       $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
18826     if( isset( $input['BYYEARDAY'] ))
18827       $input2['BYYEARDAY']  = $input['BYYEARDAY'];
18828     if( isset( $input['BYWEEKNO'] ))
18829       $input2['BYWEEKNO']   = $input['BYWEEKNO'];
18830     if( isset( $input['BYMONTH'] ))
18831       $input2['BYMONTH']    = $input['BYMONTH'];
18832     if( isset( $input['BYSETPOS'] ))
18833       $input2['BYSETPOS']   = $input['BYSETPOS'];
18834     if( isset( $input['WKST'] ))
18835       $input2['WKST']       = $input['WKST'];
18836     return $input2;
18837   }
18838 /**
18839  * convert format for input date to internal date with parameters
18840  *
18841  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
18842  * @since 2.14.1 - 2012-10-15
18843  * @param mixed  $year
18844  * @param mixed  $month   optional
18845  * @param int    $day     optional
18846  * @param int    $hour    optional
18847  * @param int    $min     optional
18848  * @param int    $sec     optional
18849  * @param string $tz      optional
18850  * @param array  $params  optional
18851  * @param string $caller  optional
18852  * @param string $objName optional
18853  * @param string $tzid    optional
18854  * @return array
18855  */
18856   public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) {
18857     $input = $parno = null;
18858     $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
18859     iCalUtilityFunctions::_strDate2arr( $year );
18860     if( iCalUtilityFunctions::_isArrayDate( $year )) {
18861       $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, $parno );
18862       if( 100 > $input['value']['year'] )
18863         $input['value']['year'] += 2000;
18864       if( $localtime )
18865         unset( $month['VALUE'], $month['TZID'] );
18866       elseif( !isset( $month['TZID'] ) && isset( $tzid ))
18867         $month['TZID'] = $tzid;
18868       if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
18869         unset( $month['TZID'] );
18870       elseif( isset( $month['TZID'] ) && iCalUtilityFunctions::_isOffset( $month['TZID'] )) {
18871         $input['value']['tz'] = $month['TZID'];
18872         unset( $month['TZID'] );
18873       }
18874       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
18875       $hitval          = ( isset( $input['value']['tz'] )) ? 7 : 6;
18876       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
18877       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $input['value'] ), $parno );
18878       if(( 3 != $parno ) && isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
18879         $d             = $input['value'];
18880         $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
18881         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
18882         unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
18883       }
18884       if( isset( $input['value']['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
18885         $input['params']['TZID'] = $input['value']['tz'];
18886         unset( $input['value']['tz'] );
18887       }
18888     } // end if( iCalUtilityFunctions::_isArrayDate( $year ))
18889     elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
18890       if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
18891       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
18892       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
18893       $hitval          = 7;
18894       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
18895       if( !isset( $input['params']['TZID'] ) && !empty( $tzid ))
18896         $input['params']['TZID'] = $tzid;
18897       if( isset( $year['tz'] )) {
18898         $parno         = 6;
18899         if( !iCalUtilityFunctions::_isOffset( $year['tz'] ))
18900           $input['params']['TZID'] = $year['tz'];
18901       }
18902       elseif( isset( $input['params']['TZID'] )) {
18903         $year['tz']    = $input['params']['TZID'];
18904         $parno         = 6;
18905         if( iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
18906           unset( $input['params']['TZID'] );
18907           $parno       = 7;
18908         }
18909       }
18910       $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno );
18911     } // end elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year ))
18912     elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone]
18913       if( $localtime )
18914         unset( $month['VALUE'], $month['TZID'] );
18915       elseif( !isset( $month['TZID'] ) && !empty( $tzid ))
18916         $month['TZID'] = $tzid;
18917       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
18918       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
18919       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
18920       $input['value']  = iCalUtilityFunctions::_strdate2date( $year, $parno );
18921       unset( $input['value']['unparsedtext'] );
18922       if( isset( $input['value']['tz'] )) {
18923         if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
18924           $d           = $input['value'];
18925           $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
18926           $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
18927           unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
18928         }
18929         else {
18930           $input['params']['TZID'] = $input['value']['tz'];
18931           unset( $input['value']['tz'] );
18932         }
18933       }
18934       elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
18935         $d             = $input['value'];
18936         $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
18937         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
18938         unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
18939       }
18940     } // end elseif( 8 <= strlen( trim( $year )))
18941     else {
18942       if( is_array( $params ))
18943         $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
18944       elseif( is_array( $tz )) {
18945         $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' ));
18946         $tz = FALSE;
18947       }
18948       elseif( is_array( $hour )) {
18949         $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' ));
18950         $hour = $min = $sec = $tz = FALSE;
18951       }
18952       if( $localtime )
18953         unset ( $input['params']['VALUE'], $input['params']['TZID'] );
18954       elseif( !isset( $tz ) && !isset( $input['params']['TZID'] ) && !empty( $tzid ))
18955         $input['params']['TZID'] = $tzid;
18956       elseif( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz ))
18957         unset( $input['params']['TZID'] );
18958       elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
18959         $tz            = $input['params']['TZID'];
18960         unset( $input['params']['TZID'] );
18961       }
18962       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
18963       $hitval          = ( iCalUtilityFunctions::_isOffset( $tz )) ? 7 : 6;
18964       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
18965       $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day );
18966       if( 3 != $parno ) {
18967         $input['value']['hour'] = ( $hour ) ? $hour : '0';
18968         $input['value']['min']  = ( $min )  ? $min  : '0';
18969         $input['value']['sec']  = ( $sec )  ? $sec  : '0';
18970         if( !empty( $tz ))
18971           $input['value']['tz'] = $tz;
18972         $strdate       = iCalUtilityFunctions::_date2strdate( $input['value'], $parno );
18973         if( !empty( $tz ) && !iCalUtilityFunctions::_isOffset( $tz ))
18974           $strdate    .= ( 'Z' == $tz ) ? $tz : ' '.$tz;
18975         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
18976         unset( $input['value']['unparsedtext'] );
18977         if( isset( $input['value']['tz'] )) {
18978           if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
18979             $d           = $input['value'];
18980             $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
18981             $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
18982             unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
18983           }
18984           else {
18985             $input['params']['TZID'] = $input['value']['tz'];
18986             unset( $input['value']['tz'] );
18987           }
18988         }
18989         elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
18990           $d             = $input['value'];
18991           $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
18992           $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
18993           unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
18994         }
18995       }
18996     } // end else (i.e. using all arguments)
18997     if(( 3 == $parno ) || ( isset( $input['params']['VALUE'] ) && ( 'DATE' == $input['params']['VALUE'] ))) {
18998       $input['params']['VALUE'] = 'DATE';
18999       unset( $input['value']['hour'], $input['value']['min'], $input['value']['sec'], $input['value']['tz'], $input['params']['TZID'] );
19000     }
19001     elseif( isset( $input['params']['TZID'] )) {
19002       if(( 'UTC' == strtoupper( $input['params']['TZID'] )) || ( 'GMT' == strtoupper( $input['params']['TZID'] ))) {
19003         $input['value']['tz'] = 'Z';
19004         unset( $input['params']['TZID'] );
19005       }
19006       else
19007         unset( $input['value']['tz'] );
19008     }
19009     elseif( isset( $input['value']['tz'] )) {
19010       if(( 'UTC' == strtoupper( $input['value']['tz'] )) || ( 'GMT' == strtoupper( $input['value']['tz'] )))
19011         $input['value']['tz'] = 'Z';
19012       if( 'Z' != $input['value']['tz'] ) {
19013         $input['params']['TZID'] = $input['value']['tz'];
19014         unset( $input['value']['tz'] );
19015       }
19016       else
19017         unset( $input['params']['TZID'] );
19018     }
19019     if( $localtime )
19020       unset( $input['value']['tz'], $input['params']['TZID'] );
19021     return $input;
19022   }
19023 /**
19024  * convert format for input date (UTC) to internal date with parameters
19025  *
19026  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19027  * @since 2.14.4 - 2012-10-06
19028  * @param mixed $year
19029  * @param mixed $month  optional
19030  * @param int   $day    optional
19031  * @param int   $hour   optional
19032  * @param int   $min    optional
19033  * @param int   $sec    optional
19034  * @param array $params optional
19035  * @return array
19036  */
19037   public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
19038     $input = null;
19039     iCalUtilityFunctions::_strDate2arr( $year );
19040     if( iCalUtilityFunctions::_isArrayDate( $year )) {
19041       $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, 7 );
19042       if( isset( $input['value']['year'] ) && ( 100 > $input['value']['year'] ))
19043         $input['value']['year'] += 2000;
19044       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
19045       if( isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
19046         $d             = $input['value'];
19047         $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
19048         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
19049         unset( $input['value']['unparsedtext'] );
19050       }
19051     }
19052     elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
19053       $year['tz']      = 'UTC';
19054       $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 );
19055       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
19056     }
19057     elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
19058       $input['value']  = iCalUtilityFunctions::_strdate2date( $year, 7 );
19059       unset( $input['value']['unparsedtext'] );
19060       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
19061     }
19062     else {
19063       $input['value']  = array( 'year'  => $year
+19064                               , 'month' => $month
+19065                               , 'day'   => $day
+19066                               , 'hour'  => $hour
+19067                               , 'min'   => $min
+19068                               , 'sec'   => $sec );
19069       if(  isset( $tz )) $input['value']['tz'] = $tz;
19070       if(( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) ||
+19071          ( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))) {
19072           if( !isset( $tz ) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
19073             $input['value']['tz'] = $input['params']['TZID'];
19074           unset( $input['params']['TZID'] );
19075         $strdate        = iCalUtilityFunctions::_date2strdate( $input['value'], 7 );
19076         $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
19077         unset( $input['value']['unparsedtext'] );
19078       }
19079       $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
19080     }
19081     $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
19082     if( !isset( $input['value']['hour'] )) $input['value']['hour'] = 0;
19083     if( !isset( $input['value']['min'] ))  $input['value']['min']  = 0;
19084     if( !isset( $input['value']['sec'] ))  $input['value']['sec']  = 0;
19085     $input['value']['tz'] = 'Z';
19086     return $input;
19087   }
19088 /**
19089  * check index and set (an indexed) content in multiple value array
19090  *
19091  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19092  * @since 2.6.12 - 2011-01-03
19093  * @param array $valArr
19094  * @param mixed $value
19095  * @param array $params
19096  * @param array $defaults
19097  * @param int $index
19098  * @return void
19099  */
19100   public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
19101     if( !is_array( $valArr )) $valArr = array();
19102     if( $index )
19103       $index = $index - 1;
19104     elseif( 0 < count( $valArr )) {
19105       $keys  = array_keys( $valArr );
19106       $index = end( $keys ) + 1;
19107     }
19108     else
19109       $index = 0;
19110     $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
19111     ksort( $valArr );
19112   }
19113 /**
19114  * set input (formatted) parameters- component property attributes
19115  *
19116  * default parameters can be set, if missing
19117  *
19118  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19119  * @since 1.x.x - 2007-05-01
19120  * @param array $params
19121  * @param array $defaults
19122  * @return array
19123  */
19124   public static function _setParams( $params, $defaults=FALSE ) {
19125     if( !is_array( $params))
19126       $params = array();
19127     $input = array();
19128     foreach( $params as $paramKey => $paramValue ) {
19129       if( is_array( $paramValue )) {
19130         foreach( $paramValue as $pkey => $pValue ) {
19131           if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
19132             $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
19133         }
19134       }
19135       elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
19136         $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
19137       if( 'VALUE' == strtoupper( $paramKey ))
19138         $input['VALUE']                 = strtoupper( $paramValue );
19139       else
19140         $input[strtoupper( $paramKey )] = $paramValue;
19141     }
19142     if( is_array( $defaults )) {
19143       foreach( $defaults as $paramKey => $paramValue ) {
19144         if( !isset( $input[$paramKey] ))
19145           $input[$paramKey] = $paramValue;
19146       }
19147     }
19148     return (0 < count( $input )) ? $input : null;
19149   }
19150 /**
19151  * break lines at pos 75
19152  *
19153  * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
19154  * break. Long content lines SHOULD be split into a multiple line
19155  * representations using a line "folding" technique. That is, a long
19156  * line can be split between any two characters by inserting a CRLF
19157  * immediately followed by a single linear white space character (i.e.,
19158  * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
19159  * of CRLF followed immediately by a single linear white space character
19160  * is ignored (i.e., removed) when processing the content type.
19161  *
19162  * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
19163  * the reserved expression "\n" in the arg $string could be broken up by the
19164  * folding of lines, causing ambiguity in the return string.
19165  *
19166  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19167  * @since 2.16.2 - 2012-12-18
19168  * @param string $value
19169  * @return string
19170  */
19171   public static function _size75( $string, $nl ) {
19172     $tmp             = $string;
19173     $string          = '';
19174     $cCnt = $x       = 0;
19175     while( TRUE ) {
19176       if( !isset( $tmp[$x] )) {
19177         $string     .= $nl;                           // loop breakes here
19178         break;
19179       }
19180       elseif(( 74   <= $cCnt ) && ( '\\'  == $tmp[$x] ) && ( 'n' == $tmp[$x+1] )) {
19181         $string     .= $nl.' \n';                     // don't break lines inside '\n'
19182         $x          += 2;
19183         if( !isset( $tmp[$x] )) {
19184           $string   .= $nl;
19185           break;
19186         }
19187         $cCnt        = 3;
19188       }
19189       elseif( 75    <= $cCnt ) {
19190         $string     .= $nl.' ';
19191         $cCnt        = 1;
19192       }
19193       $byte          = ord( $tmp[$x] );
19194       $string       .= $tmp[$x];
19195       switch( TRUE ) { // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
19196         case(( $byte >= 0x20 ) && ( $byte <= 0x7F )): // characters U-00000000 - U-0000007F (same as ASCII)
19197           $cCnt     += 1;
19198           break;                                      // add a one byte character
19199         case(( $byte & 0xE0) == 0xC0 ):               // characters U-00000080 - U-000007FF, mask 110XXXXX
19200           if( isset( $tmp[$x+1] )) {
19201             $cCnt   += 1;
19202             $string  .= $tmp[$x+1];
19203             $x       += 1;                            // add a two bytes character
19204           }
19205           break;
19206         case(( $byte & 0xF0 ) == 0xE0 ):              // characters U-00000800 - U-0000FFFF, mask 1110XXXX
19207           if( isset( $tmp[$x+2] )) {
19208             $cCnt   += 1;
19209             $string .= $tmp[$x+1].$tmp[$x+2];
19210             $x      += 2;                             // add a three bytes character
19211           }
19212           break;
19213         case(( $byte & 0xF8 ) == 0xF0 ):              // characters U-00010000 - U-001FFFFF, mask 11110XXX
19214           if( isset( $tmp[$x+3] )) {
19215             $cCnt   += 1;
19216             $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3];
19217             $x      += 3;                             // add a four bytes character
19218           }
19219           break;
19220         case(( $byte & 0xFC ) == 0xF8 ):              // characters U-00200000 - U-03FFFFFF, mask 111110XX
19221           if( isset( $tmp[$x+4] )) {
19222             $cCnt   += 1;
19223             $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4];
19224             $x      += 4;                             // add a five bytes character
19225           }
19226           break;
19227         case(( $byte & 0xFE ) == 0xFC ):              // characters U-04000000 - U-7FFFFFFF, mask 1111110X
19228           if( isset( $tmp[$x+5] )) {
19229             $cCnt   += 1;
19230             $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4].$tmp[$x+5];
19231             $x      += 5;                             // add a six bytes character
19232           }
19233         default:                                      // add any other byte without counting up $cCnt
19234           break;
19235       } // end switch( TRUE )
19236       $x         += 1;                                // next 'byte' to test
19237     } // end while( TRUE ) {
19238     return $string;
19239   }
19240 /**
19241  * sort callback functions for exdate
19242  *
19243  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19244  * @since 2.16.11 - 2013-01-12
19245  * @param array $a
19246  * @param array $b
19247  * @return int
19248  */
19249   public static function _sortExdate1( $a, $b ) {
19250     $as  = sprintf( '%04d%02d%02d', $a['year'], $a['month'], $a['day'] );
19251     $as .= ( isset( $a['hour'] )) ? sprintf( '%02d%02d%02d', $a['hour'], $a['min'], $a['sec'] ) : '';
19252     $bs  = sprintf( '%04d%02d%02d', $b['year'], $b['month'], $b['day'] );
19253     $bs .= ( isset( $b['hour'] )) ? sprintf( '%02d%02d%02d', $b['hour'], $b['min'], $b['sec'] ) : '';
19254     return strcmp( $as, $bs );
19255   }
19256   public static function _sortExdate2( $a, $b ) {
19257     $val = reset( $a['value'] );
19258     $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
19259     $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
19260     $val = reset( $b['value'] );
19261     $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
19262     $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
19263     return strcmp( $as, $bs );
19264   }
19265 /**
19266  * sort callback functions for rdate
19267  *
19268  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19269  * @since 2.16.9 - 2013-01-12
19270  * @param array $a
19271  * @param array $b
19272  * @return int
19273  */
19274   public static function _sortRdate1( $a, $b ) {
19275     $val = isset( $a['year'] ) ? $a : $a[0];
19276     $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
19277     $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
19278     $val = isset( $b['year'] ) ? $b : $b[0];
19279     $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
19280     $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
19281     return strcmp( $as, $bs );
19282   }
19283   public static function _sortRdate2( $a, $b ) {
19284     $val = isset( $a['value'][0]['year'] ) ? $a['value'][0] : $a['value'][0][0];
19285     $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
19286     $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
19287     $val = isset( $b['value'][0]['year'] ) ? $b['value'][0] : $b['value'][0][0];
19288     $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
19289     $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
19290     return strcmp( $as, $bs );
19291   }
19292 /**
19293  * step date, return updated date, array and timpstamp
19294  *
19295  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19296  * @since 2.14.1 - 2012-09-24
19297  * @param array $date, date to step
19298  * @param int   $timestamp
19299  * @param array $step, default array( 'day' => 1 )
19300  * @return void
19301  */
19302   public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) {
19303     if( !isset( $date['hour'] )) $date['hour'] = 0;
19304     if( !isset( $date['min'] ))  $date['min']  = 0;
19305     if( !isset( $date['sec'] ))  $date['sec']  = 0;
19306     foreach( $step as $stepix => $stepvalue )
19307       $date[$stepix] += $stepvalue;
19308     $timestamp  = mktime( $date['hour'], $date['min'], $date['sec'], $date['month'], $date['day'], $date['year'] );
19309     $d          = date( 'Y-m-d-H-i-s', $timestamp);
19310     $d          = explode( '-', $d );
19311     $date       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
19312     foreach( $date as $k => $v )
19313       $date[$k] = (int) $v;
19314   }
19315 /**
19316  * convert a date from specific string to array format
19317  *
19318  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19319  * @since 2.11.8 - 2012-01-27
19320  * @param mixed $input
19321  * @return bool, TRUE on success
19322  */
19323   public static function _strDate2arr( & $input ) {
19324     if( is_array( $input ))
19325       return FALSE;
19326     if( 5 > strlen( (string) $input ))
19327       return FALSE;
19328     $work = $input;
19329     if( 2 == substr_count( $work, '-' ))
19330       $work = str_replace( '-', '', $work );
19331     if( 2 == substr_count( $work, '/' ))
19332       $work = str_replace( '/', '', $work );
19333     if( !ctype_digit( substr( $work, 0, 8 )))
19334       return FALSE;
19335     $temp = array( 'year'  => (int) substr( $work,  0, 4 )
+19336                  , 'month' => (int) substr( $work,  4, 2 )
+19337                  , 'day'   => (int) substr( $work,  6, 2 ));
19338     if( !checkdate( $temp['month'], $temp['day'], $temp['year'] ))
19339       return FALSE;
19340     if( 8 == strlen( $work )) {
19341       $input = $temp;
19342       return TRUE;
19343     }
19344     if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
19345       $work =  substr( $work, 9 );
19346     elseif( ctype_digit( substr( $work, 8, 1 )))
19347       $work = substr( $work, 8 );
19348     else
19349      return FALSE;
19350     if( 2 == substr_count( $work, ':' ))
19351       $work = str_replace( ':', '', $work );
19352     if( !ctype_digit( substr( $work, 0, 4 )))
19353       return FALSE;
19354     $temp['hour']  = substr( $work, 0, 2 );
19355     $temp['min']   = substr( $work, 2, 2 );
19356     if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
+19357        (( 0 > $temp['min'] )  || ( $temp['min']  > 59 )))
19358       return FALSE;
19359     if( ctype_digit( substr( $work, 4, 2 ))) {
19360       $temp['sec'] = substr( $work, 4, 2 );
19361       if((  0 > $temp['sec'] ) || ( $temp['sec']  > 59 ))
19362         return FALSE;
19363       $len = 6;
19364     }
19365     else {
19366       $temp['sec'] = 0;
19367       $len = 4;
19368     }
19369     if( $len < strlen( $work))
19370       $temp['tz'] = trim( substr( $work, 6 ));
19371     $input = $temp;
19372     return TRUE;
19373   }
19374 /**
19375  * ensures internal date-time/date format for input date-time/date in string fromat
19376  *
19377  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19378  * @since 2.14.1 - 2012-10-07
19379  * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
19380  * @param array $datetime
19381  * @param int   $parno optional, default FALSE
19382  * @param moxed $wtz optional, default null
19383  * @return array
19384  */
19385   public static function _date_time_string( $datetime, $parno=FALSE ) {
19386     return iCalUtilityFunctions::_strdate2date( $datetime, $parno, null );
19387   }
19388   public static function _strdate2date( $datetime, $parno=FALSE, $wtz=null ) {
19389     // save original input string to return it later
19390     $unparseddatetime = $datetime;
19391     $datetime   = (string) trim( $datetime );
19392     $tz         = null;
19393     $offset     = 0;
19394     $tzSts      = FALSE;
19395     $len        = strlen( $datetime );
19396     if( 'Z' == substr( $datetime, -1 )) {
19397       $tz       = 'Z';
19398       $datetime = trim( substr( $datetime, 0, ( $len - 1 )));
19399       $tzSts    = TRUE;
19400       $len      = 88;
19401     }
19402     if( iCalUtilityFunctions::_isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset
19403       $tz       = substr( $datetime, -5, 5 );
19404       $datetime = trim( substr( $datetime, 0, ($len - 5)));
19405       $len      = strlen( $datetime );
19406     }
19407     elseif( iCalUtilityFunctions::_isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset
19408       $tz       = substr( $datetime, -7, 7 );
19409       $datetime = trim( substr( $datetime, 0, ($len - 7)));
19410       $len      = strlen( $datetime );
19411     }
19412     elseif( empty( $wtz ) && ctype_digit( substr( $datetime, 0, 4 )) && ctype_digit( substr( $datetime, -2, 2 )) && iCalUtilityFunctions::_strDate2arr( $datetime )) {
19413       $output = $datetime;
19414       if( !empty( $tz ))
19415         $output['tz'] = 'Z';
19416       $output['unparsedtext'] = $unparseddatetime;
19417       return $output;
19418     }
19419     else {
19420       $cx  = $tx = 0;    //  find any trailing timezone or offset
19421       for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
19422         $char = substr( $datetime, $cx, 1 );
19423         if(( ' ' == $char) || ctype_digit( $char ))
19424           break; // if exists, tz ends here.. . ?
19425         else
19426            $tx--; // tz length counter
19427       }
19428       if( 0 > $tx ) { // if any
19429         $tz     = substr( $datetime, $tx );
19430         $datetime = trim( substr( $datetime, 0, $len + $tx ));
19431         $len    = strlen( $datetime );
19432       }
19433       if(( 17 <= $len ) ||  // long textual datetime
+19434          ( ctype_digit( substr( $datetime, 0, 8 )) && ( 'T' ==  substr( $datetime, 8, 1 )) && ctype_digit( substr( $datetime, -6, 6 ))) ||
+19435          ( ctype_digit( substr( $datetime, 0, 14 )))) {
19436         $len    = 88;
19437         $tzSts  = TRUE;
19438       }
19439       else
19440         $tz     = null; // no tz for Y-m-d dates
19441     }
19442     if( empty( $tz ) && !empty( $wtz ))
19443       $tz       = $wtz;
19444     if( 17 >= $len ) // any Y-m-d textual date
19445       $tz       = null;
19446     if( !empty( $tz ) && ( 17 < $len )) { // tz set AND long textual datetime
19447       if(( 'Z' != $tz ) && ( iCalUtilityFunctions::_isOffset( $tz ))) {
19448         $offset = (string) iCalUtilityFunctions::_tz2offset( $tz ) * -1;
19449         $tz     = 'UTC';
19450         $tzSts  = TRUE;
19451       }
19452       elseif( !empty( $wtz ))
19453         $tzSts  = TRUE;
19454       $tz       = trim( $tz );
19455       if(( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
19456         $tz     = 'UTC';
19457       if( 0 < substr_count( $datetime, '-' ))
19458         $datetime = str_replace( '-', '/', $datetime );
19459       try {
19460         $d        = new DateTime( $datetime, new DateTimeZone( $tz ));
19461         if( 0  != $offset )  // adjust for offset
19462           $d->modify( $offset.' seconds' );
19463         $datestring = $d->format( 'Y-m-d-H-i-s' );
19464         unset( $d );
19465       }
19466       catch( Exception $e ) {
19467         $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
19468       }
19469     } // end if( !empty( $tz ) && ( 17 < $len ))
19470     else
19471       $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
19472 // echo "<tr><td>&nbsp;<td colspan='3'>_strdate2date input=$datetime, tz=$tz, offset=$offset, wtz=$wtz, len=$len, prepDate=$datestring\n";
19473     if( 'UTC' == $tz )
19474       $tz         = 'Z';
19475     $d            = explode( '-', $datestring );
19476     $output       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2] );
19477     if((( FALSE !== $parno ) && ( 3 != $parno )) || // parno is set to 6 or 7
+19478        (( FALSE === $parno ) && ( 'Z' == $tz ))  || // parno is not set and UTC
+19479        (( FALSE === $parno ) && ( 'Z' != $tz ) && ( 0 != $d[3] + $d[4] + $d[5] ) && ( 17 < $len ))) { // !parno and !UTC and 0 != hour+min+sec and long input text
19480       $output['hour'] = $d[3];
19481       $output['min']  = $d[4];
19482       $output['sec']  = $d[5];
19483       if(( $tzSts || ( 7 == $parno )) && !empty( $tz ))
19484         $output['tz'] = $tz;
19485     }
19486     // return original string in the array in case strtotime failed to make sense of it
19487     $output['unparsedtext'] = $unparseddatetime;
19488     return $output;
19489   }
19490 /********************************************************************************/
19491 /**
19492  * special characters management output
19493  *
19494  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19495  * @since 2.16.2 - 2012-12-18
19496  * @param string $string
19497  * @param string $format
19498  * @param string $nl
19499  * @return string
19500  */
19501   public static function _strrep( $string, $format, $nl ) {
19502     switch( $format ) {
19503       case 'xcal':
19504         $string = str_replace( '\n',  $nl, $string);
19505         $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
19506         break;
19507       default:
19508         $pos = 0;
19509         $specChars = array( 'n', 'N', 'r', ',', ';' );
19510         while( isset( $string[$pos] )) {
19511           if( FALSE === ( $pos = strpos( $string, "\\", $pos )))
19512             break;
19513           if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
19514             $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
19515             $pos += 1;
19516           }
19517           $pos += 1;
19518         }
19519         if( FALSE !== strpos( $string, '"' ))
19520           $string = str_replace('"',   "'",       $string);
19521         if( FALSE !== strpos( $string, ',' ))
19522           $string = str_replace(',',   '\,',      $string);
19523         if( FALSE !== strpos( $string, ';' ))
19524           $string = str_replace(';',   '\;',      $string);
19525         if( FALSE !== strpos( $string, "\r\n" ))
19526           $string = str_replace( "\r\n", '\n',    $string);
19527         elseif( FALSE !== strpos( $string, "\r" ))
19528           $string = str_replace( "\r", '\n',      $string);
19529         elseif( FALSE !== strpos( $string, "\n" ))
19530           $string = str_replace( "\n", '\n',      $string);
19531         if( FALSE !== strpos( $string, '\N' ))
19532           $string = str_replace( '\N', '\n',      $string);
19533 //        if( FALSE !== strpos( $string, $nl ))
19534           $string = str_replace( $nl, '\n', $string);
19535         break;
19536     }
19537     return $string;
19538   }
19539 /**
19540  * special characters management input (from iCal file)
19541  *
19542  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19543  * @since 2.16.2 - 2012-12-18
19544  * @param string $string
19545  * @return string
19546  */
19547   public static function _strunrep( $string ) {
19548     $string = str_replace( '\\\\', '\\',     $string);
19549     $string = str_replace( '\,',   ',',      $string);
19550     $string = str_replace( '\;',   ';',      $string);
19551 //    $string = str_replace( '\n',  $nl, $string); // ??
19552     return $string;
19553   }
19554 /**
19555  * convert timestamp to date array, default UTC or adjusted for offset/timezone
19556  *
19557  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19558  * @since 2.15.1 - 2012-10-17
19559  * @param mixed   $timestamp
19560  * @param int     $parno
19561  * @param string  $wtz
19562  * @return array
19563  */
19564   public static function _timestamp2date( $timestamp, $parno=6, $wtz=null ) {
19565     if( is_array( $timestamp )) {
19566       $tz        = ( isset( $timestamp['tz'] )) ? $timestamp['tz'] : $wtz;
19567       $timestamp = $timestamp['timestamp'];
19568     }
19569     $tz          = ( isset( $tz )) ? $tz : $wtz;
19570     if( empty( $tz ) || ( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
19571       $tz        = 'UTC';
19572     elseif( iCalUtilityFunctions::_isOffset( $tz )) {
19573       $offset    = iCalUtilityFunctions::_tz2offset( $tz );
19574       $tz        = 'UTC';
19575     }
19576     try {
19577       $d         = new DateTime( "@$timestamp" );  // set UTC date
19578       if( isset( $offset ) && ( 0 != $offset ))    // adjust for offset
19579         $d->modify( $offset.' seconds' );
19580       elseif( 'UTC' != $tz )
19581         $d->setTimezone( new DateTimeZone( $tz )); // convert to local date
19582       $date      = $d->format( 'Y-m-d-H-i-s' );
19583       unset( $d );
19584     }
19585     catch( Exception $e ) {
19586       $date      = date( 'Y-m-d-H-i-s', $timestamp );
19587     }
19588     $date        = explode( '-', $date );
19589     $output      = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2] );
19590     if( 3 != $parno ) {
19591       $output['hour'] = $date[3];
19592       $output['min']  = $date[4];
19593       $output['sec']  = $date[5];
19594       if( 'UTC' == $tz && ( !isset( $offset ) || ( 0 == $offset )))
19595         $output['tz'] = 'Z';
19596     }
19597     return $output;
19598   }
19599 /**
19600  * convert timestamp (seconds) to duration in array format
19601  *
19602  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19603  * @since 2.6.23 - 2010-10-23
19604  * @param int $timestamp
19605  * @return array, duration format
19606  */
19607   public static function _timestamp2duration( $timestamp ) {
19608     $dur         = array();
19609     $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 ));
19610     $timestamp   =              $timestamp % ( 7 * 24 * 60 * 60 );
19611     $dur['day']  = (int) floor( $timestamp / ( 24 * 60 * 60 ));
19612     $timestamp   =              $timestamp % ( 24 * 60 * 60 );
19613     $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 ));
19614     $timestamp   =              $timestamp % ( 60 * 60 );
19615     $dur['min']  = (int) floor( $timestamp / ( 60 ));
19616     $dur['sec']  = (int)        $timestamp % ( 60 );
19617     return $dur;
19618   }
19619 /**
19620  * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
19621  *
19622  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19623  * @since 2.15.1 - 2012-10-17
19624  * @param mixed  $date,   date to alter
19625  * @param string $tzFrom, PHP valid 'from' timezone
19626  * @param string $tzTo,   PHP valid 'to' timezone, default 'UTC'
19627  * @param string $format, date output format, default 'Ymd\THis'
19628  * @return bool
19629  */
19630   public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
19631     if( is_array( $date ) && isset( $date['timestamp'] )) {
19632       try {
19633         $d = new DateTime( "@{$date['timestamp']}" ); // set UTC date
19634         $d->setTimezone(new DateTimeZone( $tzFrom )); // convert to 'from' date
19635       }
19636       catch( Exception $e ) { return FALSE; }
19637     }
19638     else {
19639       if( iCalUtilityFunctions::_isArrayDate( $date )) {
19640         if( isset( $date['tz'] ))
19641           unset( $date['tz'] );
19642         $date  = iCalUtilityFunctions::_date2strdate( iCalUtilityFunctions::_chkDateArr( $date ));
19643       }
19644       if( 'Z' == substr( $date, -1 ))
19645         $date = substr( $date, 0, ( strlen( $date ) - 2 ));
19646       try { $d = new DateTime( $date, new DateTimeZone( $tzFrom )); }
19647       catch( Exception $e ) { return FALSE; }
19648     }
19649     try { $d->setTimezone( new DateTimeZone( $tzTo )); }
19650     catch( Exception $e ) { return FALSE; }
19651     $date = $d->format( $format );
19652     return TRUE;
19653   }
19654 /**
19655  * convert offset, [+/-]HHmm[ss], to seconds, used when correcting UTC to localtime or v.v.
19656  *
19657  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19658  * @since 2.11.4 - 2012-01-11
19659  * @param string $offset
19660  * @return integer
19661  */
19662   public static function _tz2offset( $tz ) {
19663     $tz           = trim( (string) $tz );
19664     $offset       = 0;
19665     if(((     5  != strlen( $tz ))       && ( 7  != strlen( $tz ))) ||
+19666        ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
+19667        (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
+19668            (( 7  == strlen( $tz ))       && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
19669       return $offset;
19670     $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
19671     $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
19672     $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
19673     $offset       = $hours2sec + $min2sec + $sec;
19674     $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
19675     return $offset;
19676   }
19677 }
19678 /*********************************************************************************/
19679 /*          iCalcreator vCard helper functions                                   */
19680 /*********************************************************************************/
19681 /**
19682  * convert single ATTENDEE, CONTACT or ORGANIZER (in email format) to vCard
19683  * returns vCard/TRUE or if directory (if set) or file write is unvalid, FALSE
19684  *
19685  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19686  * @since 2.12.2 - 2012-07-11
19687  * @param object $email
19688  * $param string $version, vCard version (default 2.1)
19689  * $param string $directory, where to save vCards (default FALSE)
19690  * $param string $ext, vCard file extension (default 'vcf')
19691  * @return mixed
19692  */
19693 function iCal2vCard( $email, $version='2.1', $directory=FALSE, $ext='vcf' ) {
19694   if( FALSE === ( $pos = strpos( $email, '@' )))
19695     return FALSE;
19696   if( $directory ) {
19697     if( DIRECTORY_SEPARATOR != substr( $directory, ( 0 - strlen( DIRECTORY_SEPARATOR ))))
19698       $directory .= DIRECTORY_SEPARATOR;
19699     if( !is_dir( $directory ) || !is_writable( $directory ))
19700       return FALSE;
19701   }
19702             /* prepare vCard */
19703   $email  = str_replace( 'MAILTO:', '', $email );
19704   $name   = $person = substr( $email, 0, $pos );
19705   if( ctype_upper( $name ) || ctype_lower( $name ))
19706     $name = array( $name );
19707   else {
19708     if( FALSE !== ( $pos = strpos( $name, '.' ))) {
19709       $name = explode( '.', $name );
19710       foreach( $name as $k => $part )
19711         $name[$k] = ucfirst( $part );
19712     }
19713     else { // split camelCase
19714       $chars = $name;
19715       $name  = array( $chars[0] );
19716       $k     = 0;
19717       $x     = 1;
19718       while( FALSE !== ( $char = substr( $chars, $x, 1 ))) {
19719         if( ctype_upper( $char )) {
19720           $k += 1;
19721           $name[$k] = '';
19722         }
19723         $name[$k]  .= $char;
19724         $x++;
19725       }
19726     }
19727   }
19728   $nl     = "\r\n";
19729   $FN     = 'FN:'.implode( ' ', $name ).$nl;
19730   $name   = array_reverse( $name );
19731   $N      = 'N:'.array_shift( $name );
19732   $scCnt  = 0;
19733   while( NULL != ( $part = array_shift( $name ))) {
19734     if(( '4.0' != $version ) || ( 4 > $scCnt ))
19735       $scCnt += 1;
19736     $N   .= ';'.$part;
19737   }
19738   while(( '4.0' == $version ) && ( 4 > $scCnt )) {
19739     $N   .= ';';
19740     $scCnt += 1;
19741   }
19742   $N     .= $nl;
19743   $EMAIL  = 'EMAIL:'.$email.$nl;
19744            /* create vCard */
19745   $vCard  = 'BEGIN:VCARD'.$nl;
19746   $vCard .= "VERSION:$version$nl";
19747   $vCard .= 'PRODID:-//kigkonsult.se '.ICALCREATOR_VERSION."//$nl";
19748   $vCard .= $N;
19749   $vCard .= $FN;
19750   $vCard .= $EMAIL;
19751   $vCard .= 'REV:'.gmdate( 'Ymd\THis\Z' ).$nl;
19752   $vCard .= 'END:VCARD'.$nl;
19753             /* save each vCard as (unique) single file */
19754   if( $directory ) {
19755     $fname = $directory.preg_replace( '/[^a-z0-9.]/i', '', $email );
19756     $cnt   = 1;
19757     $dbl   = '';
19758     while( is_file ( $fname.$dbl.'.'.$ext )) {
19759       $cnt += 1;
19760       $dbl = "_$cnt";
19761     }
19762     if( FALSE === file_put_contents( $fname, $fname.$dbl.'.'.$ext ))
19763       return FALSE;
19764     return TRUE;
19765   }
19766             /* return vCard */
19767   else
19768     return $vCard;
19769 }
19770 /**
19771  * convert ATTENDEEs, CONTACTs and ORGANIZERs (in email format) to vCards
19772  *
19773  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19774  * @since 2.12.2 - 2012-05-07
19775  * @param object $calendar, iCalcreator vcalendar instance reference
19776  * $param string $version, vCard version (default 2.1)
19777  * $param string $directory, where to save vCards (default FALSE)
19778  * $param string $ext, vCard file extension (default 'vcf')
19779  * @return mixed
19780  */
19781 function iCal2vCards( & $calendar, $version='2.1', $directory=FALSE, $ext='vcf' ) {
19782   $hits   = array();
19783   $vCardP = array( 'ATTENDEE', 'CONTACT', 'ORGANIZER' );
19784   foreach( $vCardP as $prop ) {
19785     $hits2 = $calendar->getProperty( $prop );
19786     foreach( $hits2 as $propValue => $occCnt ) {
19787       if( FALSE === ( $pos = strpos( $propValue, '@' )))
19788         continue;
19789       $propValue = str_replace( 'MAILTO:', '', $propValue );
19790       if( isset( $hits[$propValue] ))
19791         $hits[$propValue] += $occCnt;
19792       else
19793         $hits[$propValue]  = $occCnt;
19794     }
19795   }
19796   if( empty( $hits ))
19797     return FALSE;
19798   ksort( $hits );
19799   $output   = '';
19800   foreach( $hits as $email => $skip ) {
19801     $res = iCal2vCard( $email, $version, $directory, $ext );
19802     if( $directory && !$res )
19803       return FALSE;
19804     elseif( !$res )
19805       return $res;
19806     else
19807       $output .= $res;
19808   }
19809   if( $directory )
19810     return TRUE;
19811   if( !empty( $output ))
19812     return $output;
19813   return FALSE;
19814 }
19815 /*********************************************************************************/
19816 /*          iCalcreator XML (rfc6321) helper functions                           */
19817 /*********************************************************************************/
19818 /**
19819  * format iCal XML output, rfc6321, using PHP SimpleXMLElement
19820  *
19821  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
19822  * @since 2.15.6 - 2012-10-19
19823  * @param object $calendar, iCalcreator vcalendar instance reference
19824  * @return string
19825  */
19826 function iCal2XML( & $calendar ) {
19827             /** fix an SimpleXMLElement instance and create root element */
19828   $xmlstr     = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
19829   $xmlstr    .= '<!-- created utilizing kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
19830   $xmlstr    .= '</icalendar>';
19831   $xml        = new SimpleXMLElement( $xmlstr );
19832   $vcalendar  = $xml->addChild( 'vcalendar' );
19833             /** fix calendar properties */
19834   $properties = $vcalendar->addChild( 'properties' );
19835   $calProps = array( 'prodid', 'version', 'calscale', 'method' );
19836   foreach( $calProps as $calProp ) {
19837     if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
19838       _addXMLchild( $properties, $calProp, 'text', $content );
19839   }
19840   while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
19841     _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
19842   $langCal = $calendar->getConfig( 'language' );
19843             /** prepare to fix components with properties */
19844   $components    = $vcalendar->addChild( 'components' );
19845   $comps         = array( 'vtimezone', 'vevent', 'vtodo', 'vjournal', 'vfreebusy' );
19846   foreach( $comps as $compName ) {
19847     switch( $compName ) {
19848       case 'vevent':
19849       case 'vtodo':
19850         $subComps     = array( 'valarm' );
19851         break;
19852       case 'vjournal':
19853       case 'vfreebusy':
19854         $subComps     = array();
19855         break;
19856       case 'vtimezone':
19857         $subComps     = array( 'standard', 'daylight' );
19858         break;
19859     } // end switch( $compName )
19860             /** fix component properties */
19861     while( FALSE !== ( $component = $calendar->getComponent( $compName ))) {
19862       $child      = $components->addChild( $compName );
19863       $properties = $child->addChild( 'properties' );
19864       $langComp   = $component->getConfig( 'language' );
19865       $props      = $component->getConfig( 'setPropertyNames' );
19866       foreach( $props as $prop ) {
19867         switch( strtolower( $prop )) {
19868           case 'attach':          // may occur multiple times, below
19869             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19870               $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
19871               unset( $content['params']['VALUE'] );
19872               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
19873             }
19874             break;
19875           case 'attendee':
19876             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19877               if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
19878                 if( $langComp )
19879                   $content['params']['LANGUAGE'] = $langComp;
19880                 elseif( $langCal )
19881                   $content['params']['LANGUAGE'] = $langCal;
19882               }
19883               _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
19884             }
19885             break;
19886           case 'exdate':
19887             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19888               $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
19889               unset( $content['params']['VALUE'] );
19890               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
19891             }
19892             break;
19893           case 'freebusy':
19894             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19895               if( is_array( $content ) && isset( $content['value']['fbtype'] )) {
19896                 $content['params']['FBTYPE'] = $content['value']['fbtype'];
19897                 unset( $content['value']['fbtype'] );
19898               }
19899               _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
19900             }
19901             break;
19902           case 'request-status':
19903             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19904               if( !isset( $content['params']['LANGUAGE'] )) {
19905                 if( $langComp )
19906                   $content['params']['LANGUAGE'] = $langComp;
19907                 elseif( $langCal )
19908                   $content['params']['LANGUAGE'] = $langCal;
19909               }
19910               _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
19911             }
19912             break;
19913           case 'rdate':
19914             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19915               $type = 'date-time';
19916               if( isset( $content['params']['VALUE'] )) {
19917                 if( 'DATE' == $content['params']['VALUE'] )
19918                   $type = 'date';
19919                 elseif( 'PERIOD' == $content['params']['VALUE'] )
19920                   $type = 'period';
19921               }
19922               unset( $content['params']['VALUE'] );
19923               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
19924             }
19925             break;
19926           case 'categories':
19927           case 'comment':
19928           case 'contact':
19929           case 'description':
19930           case 'related-to':
19931           case 'resources':
19932             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19933               if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
19934                 if( $langComp )
19935                   $content['params']['LANGUAGE'] = $langComp;
19936                 elseif( $langCal )
19937                   $content['params']['LANGUAGE'] = $langCal;
19938               }
19939               _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
19940             }
19941             break;
19942           case 'x-prop':
19943             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
19944               _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
19945             break;
19946           case 'created':         // single occurence below, if set
19947           case 'completed':
19948           case 'dtstamp':
19949           case 'last-modified':
19950             $utcDate = TRUE;
19951           case 'dtstart':
19952           case 'dtend':
19953           case 'due':
19954           case 'recurrence-id':
19955             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19956               $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
19957               unset( $content['params']['VALUE'] );
19958               if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
19959                 unset( $content['params']['TZID'] );
19960               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
19961             }
19962             unset( $utcDate );
19963             break;
19964           case 'duration':
19965             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19966               if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
19967                 $content['params']['RELATED'] = 'END';
19968               _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
19969             }
19970             break;
19971           case 'rrule':
19972             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
19973               _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
19974             break;
19975           case 'class':
19976           case 'location':
19977           case 'status':
19978           case 'summary':
19979           case 'transp':
19980           case 'tzid':
19981           case 'uid':
19982             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19983               if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
19984                 if( $langComp )
19985                   $content['params']['LANGUAGE'] = $langComp;
19986                 elseif( $langCal )
19987                   $content['params']['LANGUAGE'] = $langCal;
19988               }
19989               _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
19990             }
19991             break;
19992           case 'geo':
19993             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
19994               _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
19995             break;
19996           case 'organizer':
19997             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
19998               if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
19999                 if( $langComp )
20000                   $content['params']['LANGUAGE'] = $langComp;
20001                 elseif( $langCal )
20002                   $content['params']['LANGUAGE'] = $langCal;
20003               }
20004               _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
20005             }
20006             break;
20007           case 'percent-complete':
20008           case 'priority':
20009           case 'sequence':
20010             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
20011               _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
20012             break;
20013           case 'tzurl':
20014           case 'url':
20015             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
20016               _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
20017             break;
20018         } // end switch( $prop )
20019       } // end foreach( $props as $prop )
20020             /** fix subComponent properties, if any */
20021       foreach( $subComps as $subCompName ) {
20022         while( FALSE !== ( $subcomp = $component->getComponent( $subCompName ))) {
20023           $child2       = $child->addChild( $subCompName );
20024           $properties   = $child2->addChild( 'properties' );
20025           $langComp     = $subcomp->getConfig( 'language' );
20026           $subCompProps = $subcomp->getConfig( 'setPropertyNames' );
20027           foreach( $subCompProps as $prop ) {
20028             switch( strtolower( $prop )) {
20029               case 'attach':          // may occur multiple times, below
20030                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
20031                   $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
20032                   unset( $content['params']['VALUE'] );
20033                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
20034                 }
20035                 break;
20036               case 'attendee':
20037                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
20038                   if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
20039                     if( $langComp )
20040                       $content['params']['LANGUAGE'] = $langComp;
20041                     elseif( $langCal )
20042                       $content['params']['LANGUAGE'] = $langCal;
20043                   }
20044                   _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
20045                 }
20046                 break;
20047               case 'comment':
20048               case 'tzname':
20049                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
20050                   if( !isset( $content['params']['LANGUAGE'] )) {
20051                     if( $langComp )
20052                       $content['params']['LANGUAGE'] = $langComp;
20053                     elseif( $langCal )
20054                       $content['params']['LANGUAGE'] = $langCal;
20055                   }
20056                   _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
20057                 }
20058                 break;
20059               case 'rdate':
20060                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
20061                   $type = 'date-time';
20062                   if( isset( $content['params']['VALUE'] )) {
20063                     if( 'DATE' == $content['params']['VALUE'] )
20064                       $type = 'date';
20065                     elseif( 'PERIOD' == $content['params']['VALUE'] )
20066                       $type = 'period';
20067                   }
20068                   unset( $content['params']['VALUE'] );
20069                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
20070                 }
20071                 break;
20072               case 'x-prop':
20073                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
20074                   _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
20075                 break;
20076               case 'action':      // single occurence below, if set
20077               case 'description':
20078               case 'summary':
20079                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
20080                   if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
20081                     if( $langComp )
20082                       $content['params']['LANGUAGE'] = $langComp;
20083                     elseif( $langCal )
20084                       $content['params']['LANGUAGE'] = $langCal;
20085                   }
20086                   _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
20087                 }
20088                 break;
20089               case 'dtstart':
20090                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
20091                   unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
20092                   _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
20093                 }
20094                 break;
20095               case 'duration':
20096                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
20097                   _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
20098                 break;
20099               case 'repeat':
20100                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
20101                   _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
20102                 break;
20103               case 'trigger':
20104                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
20105                   if( isset( $content['value']['year'] )   &&
20106                       isset( $content['value']['month'] )  &&
20107                       isset( $content['value']['day'] ))
20108                     $type = 'date-time';
20109                   else {
20110                     $type = 'duration';
20111                     if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
20112                       $content['params']['RELATED'] = 'END';
20113                   }
20114                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
20115                 }
20116                 break;
20117               case 'tzoffsetto':
20118               case 'tzoffsetfrom':
20119                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
20120                   _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
20121                 break;
20122               case 'rrule':
20123                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
20124                   _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
20125                 break;
20126             } // switch( $prop )
20127           } // end foreach( $subCompProps as $prop )
20128         } // end while( FALSE !== ( $subcomp = $component->getComponent( subCompName )))
20129       } // end foreach( $subCombs as $subCompName )
20130     } // end while( FALSE !== ( $component = $calendar->getComponent( $compName )))
20131   } // end foreach( $comps as $compName)
20132   return $xml->asXML();
20133 }
20134 /**
20135  * Add children to a SimpleXMLelement
20136  *
20137  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20138  * @since 2.15.5 - 2012-10-19
20139  * @param object $parent,  reference to a SimpleXMLelement node
20140  * @param string $name,    new element node name
20141  * @param string $type,    content type, subelement(-s) name
20142  * @param string $content, new subelement content
20143  * @param array  $params,  new element 'attributes'
20144  * @return void
20145  */
20146 function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
20147             /** create new child node */
20148   $name  = strtolower( $name );
20149   $child = $parent->addChild( $name );
20150   if( isset( $params['VALUE'] ))
20151     unset( $params['VALUE'] );
20152   if( !empty( $params )) {
20153     $parameters = $child->addChild( 'parameters' );
20154     foreach( $params as $param => $parVal ) {
20155       $param = strtolower( $param );
20156       if( 'x-' == substr( $param, 0, 2  )) {
20157         $p1 = $parameters->addChild( $param );
20158         $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
20159       }
20160       else {
20161         $p1 = $parameters->addChild( $param );
20162         switch( $param ) {
20163           case 'altrep':
20164           case 'dir':            $ptype = 'uri';            break;
20165           case 'delegated-from':
20166           case 'delegated-to':
20167           case 'member':
20168           case 'sent-by':        $ptype = 'cal-address';    break;
20169           case 'rsvp':           $ptype = 'boolean';        break ;
20170           default:               $ptype = 'text';           break;
20171         }
20172         if( is_array( $parVal )) {
20173           foreach( $parVal as $pV )
20174             $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
20175         }
20176         else
20177           $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
20178       }
20179     }
20180   }
20181   if( empty( $content ) && ( '0' != $content ))
20182     return;
20183             /** store content */
20184   switch( $type ) {
20185     case 'binary':
20186       $v = $child->addChild( $type, $content );
20187       break;
20188     case 'boolean':
20189       break;
20190     case 'cal-address':
20191       $v = $child->addChild( $type, $content );
20192       break;
20193     case 'date':
20194       if( array_key_exists( 'year', $content ))
20195         $content = array( $content );
20196       foreach( $content as $date ) {
20197         $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] );
20198         $v = $child->addChild( $type, $str );
20199       }
20200       break;
20201     case 'date-time':
20202       if( array_key_exists( 'year', $content ))
20203         $content = array( $content );
20204       foreach( $content as $dt ) {
20205         if( !isset( $dt['hour'] )) $dt['hour'] = 0;
20206         if( !isset( $dt['min'] ))  $dt['min']  = 0;
20207         if( !isset( $dt['sec'] ))  $dt['sec']  = 0;
20208         $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
20209         if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
20210           $str .= 'Z';
20211         $v = $child->addChild( $type, $str );
20212       }
20213       break;
20214     case 'duration':
20215       $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
20216       $v = $child->addChild( $type, $output.iCalUtilityFunctions::_duration2str( $content ) );
20217       break;
20218     case 'geo':
20219       $v1 = $child->addChild( 'latitude',  number_format( (float) $content['latitude'],  6, '.', '' ));
20220       $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' ));
20221       break;
20222     case 'integer':
20223       $v = $child->addChild( $type, $content );
20224       break;
20225     case 'period':
20226       if( !is_array( $content ))
20227         break;
20228       foreach( $content as $period ) {
20229         $v1 = $child->addChild( $type );
20230         $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] );
20231         if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
20232           $str .= 'Z';
20233         $v2 = $v1->addChild( 'start', $str );
20234         if( array_key_exists( 'year', $period[1] )) {
20235           $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] );
20236           if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
20237             $str .= 'Z';
20238           $v2 = $v1->addChild( 'end', $str );
20239         }
20240         else
20241           $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_duration2str( $period[1] ));
20242       }
20243       break;
20244     case 'recur':
20245       foreach( $content as $rulelabel => $rulevalue ) {
20246         $rulelabel = strtolower( $rulelabel );
20247         switch( $rulelabel ) {
20248           case 'until':
20249             if( isset( $rulevalue['hour'] ))
20250               $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] );
20251             else
20252               $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] );
20253             $v = $child->addChild( $rulelabel, $str );
20254             break;
20255           case 'bysecond':
20256           case 'byminute':
20257           case 'byhour':
20258           case 'bymonthday':
20259           case 'byyearday':
20260           case 'byweekno':
20261           case 'bymonth':
20262           case 'bysetpos': {
20263             if( is_array( $rulevalue )) {
20264               foreach( $rulevalue as $vix => $valuePart )
20265                 $v = $child->addChild( $rulelabel, $valuePart );
20266             }
20267             else
20268               $v = $child->addChild( $rulelabel, $rulevalue );
20269             break;
20270           }
20271           case 'byday': {
20272             if( isset( $rulevalue['DAY'] )) {
20273               $str  = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
20274               $str .= $rulevalue['DAY'];
20275               $p    = $child->addChild( $rulelabel, $str );
20276             }
20277             else {
20278               foreach( $rulevalue as $valuePart ) {
20279                 if( isset( $valuePart['DAY'] )) {
20280                   $str  = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
20281                   $str .= $valuePart['DAY'];
20282                   $p    = $child->addChild( $rulelabel, $str );
20283                 }
20284                 else
20285                   $p    = $child->addChild( $rulelabel, $valuePart );
20286               }
20287             }
20288             break;
20289           }
20290           case 'freq':
20291           case 'count':
20292           case 'interval':
20293           case 'wkst':
20294           default:
20295             $p = $child->addChild( $rulelabel, $rulevalue );
20296             break;
20297         } // end switch( $rulelabel )
20298       } // end foreach( $content as $rulelabel => $rulevalue )
20299       break;
20300     case 'rstatus':
20301       $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
20302       $v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
20303       if( isset( $content['extdata'] ))
20304         $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
20305       break;
20306     case 'text':
20307       if( !is_array( $content ))
20308         $content = array( $content );
20309       foreach( $content as $part )
20310         $v = $child->addChild( $type, htmlspecialchars( $part ));
20311       break;
20312     case 'time':
20313       break;
20314     case 'uri':
20315       $v = $child->addChild( $type, $content );
20316       break;
20317     case 'utc-offset':
20318       if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
20319         $str     = substr( $content, 0, 1 );
20320         $content = substr( $content, 1 );
20321       }
20322       else
20323         $str     = '+';
20324       $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
20325       if( 4 < strlen( $content ))
20326         $str .= ':'.substr( $content, 4 );
20327       $v = $child->addChild( $type, $str );
20328       break;
20329     case 'unknown':
20330     default:
20331       if( is_array( $content ))
20332         $content = implode( '', $content );
20333       $v = $child->addChild( 'unknown', htmlspecialchars( $content ));
20334       break;
20335   }
20336 }
20337 /**
20338  * parse xml string into iCalcreator instance
20339  *
20340  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20341  * @since 2.11.2 - 2012-01-31
20342  * @param  string $xmlstr
20343  * @param  array  $iCalcfg iCalcreator config array (opt)
20344  * @return mixed  iCalcreator instance or FALSE on error
20345  */
20346 function & XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
20347   libxml_use_internal_errors( TRUE );
20348   $xml = simplexml_load_string( $xmlstr );
20349   if( !$xml ) {
20350     $str    = '';
20351     $return = FALSE;
20352     foreach( libxml_get_errors() as $error ) {
20353       switch ( $error->level ) {
20354         case LIBXML_ERR_FATAL:   $str .= ' FATAL ';   break;
20355         case LIBXML_ERR_ERROR:   $str .= ' ERROR ';   break;
20356         case LIBXML_ERR_WARNING:
20357         default:                 $str .= ' WARNING '; break;
20358       }
20359       $str .= PHP_EOL.'Error when loading XML';
20360       if( !empty( $error->file ))
20361         $str .= ',  file:'.$error->file.', ';
20362       $str .= ', line:'.$error->line;
20363       $str .= ', ('.$error->code.') '.$error->message;
20364     }
20365     error_log( $str );
20366     if( LIBXML_ERR_WARNING != $error->level )
20367       return $return;
20368     libxml_clear_errors();
20369   }
20370   return xml2iCal( $xml, $iCalcfg );
20371 }
20372 /**
20373  * parse xml file into iCalcreator instance
20374  *
20375  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20376  * @since  2.11.2 - 2012-01-20
20377  * @param  string $xmlfile
20378  * @param  array$iCalcfg iCalcreator config array (opt)
20379  * @return mixediCalcreator instance or FALSE on error
20380  */
20381 function & XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
20382   libxml_use_internal_errors( TRUE );
20383   $xml = simplexml_load_file( $xmlfile );
20384   if( !$xml ) {
20385     $str = '';
20386     foreach( libxml_get_errors() as $error ) {
20387       switch ( $error->level ) {
20388         case LIBXML_ERR_FATAL:   $str .= 'FATAL ';   break;
20389         case LIBXML_ERR_ERROR:   $str .= 'ERROR ';   break;
20390         case LIBXML_ERR_WARNING:
20391         default:                 $str .= 'WARNING '; break;
20392       }
20393       $str .= 'Failed loading XML'.PHP_EOL;
20394       if( !empty( $error->file ))
20395         $str .= ' file:'.$error->file.', ';
20396       $str .= 'line:'.$error->line.PHP_EOL;
20397       $str .= '('.$error->code.') '.$error->message.PHP_EOL;
20398     }
20399     error_log( $str );
20400     if( LIBXML_ERR_WARNING != $error->level )
20401       return FALSE;
20402     libxml_clear_errors();
20403   }
20404   return xml2iCal( $xml, $iCalcfg );
20405 }
20406 /**
20407  * parse SimpleXMLElement instance into iCalcreator instance
20408  *
20409  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20410  * @since  2.11.2 - 2012-01-27
20411  * @param  object $xmlobj  SimpleXMLElement
20412  * @param  array  $iCalcfg iCalcreator config array (opt)
20413  * @return mixed  iCalcreator instance or FALSE on error
20414  */
20415 function & XML2iCal( $xmlobj, $iCalcfg=array()) {
20416   $iCal = new vcalendar( $iCalcfg );
20417   foreach( $xmlobj->children() as $icalendar ) { // vcalendar
20418     foreach( $icalendar->children() as $calPart ) { // calendar properties and components
20419       if( 'components' == $calPart->getName()) {
20420         foreach( $calPart->children() as $component ) { // single components
20421           if( 0 < $component->count())
20422             _getXMLComponents( $iCal, $component );
20423         }
20424       }
20425       elseif(( 'properties' == $calPart->getName()) && ( 0 < $calPart->count())) {
20426         foreach( $calPart->children() as $calProp ) { // calendar properties
20427          $propName = $calProp->getName();
20428           if(( 'calscale' != $propName ) && ( 'method' != $propName ) && ( 'x-' != substr( $propName,0,2 )))
20429             continue;
20430           $params = array();
20431           foreach( $calProp->children() as $calPropElem ) { // single calendar property
20432             if( 'parameters' == $calPropElem->getName())
20433               $params = _getXMLParams( $calPropElem );
20434             else
20435               $iCal->setProperty( $propName, reset( $calPropElem ), $params );
20436           } // end foreach( $calProp->children() as $calPropElem )
20437         } // end foreach( $calPart->properties->children() as $calProp )
20438       } // end if( 0 < $calPart->properties->count())
20439     } // end foreach( $icalendar->children() as $calPart )
20440   } // end foreach( $xmlobj->children() as $icalendar )
20441   return $iCal;
20442 }
20443 /**
20444  * parse SimpleXMLElement instance property parameters and return iCalcreator property parameter array
20445  *
20446  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20447  * @since  2.11.2 - 2012-01-15
20448  * @param  object $parameters SimpleXMLElement
20449  * @return array  iCalcreator property parameter array
20450  */
20451 function _getXMLParams( & $parameters ) {
20452   if( 1 > $parameters->count())
20453     return array();
20454   $params = array();
20455   foreach( $parameters->children() as $parameter ) { // single parameter key
20456     $key   = strtoupper( $parameter->getName());
20457     $value = array();
20458     foreach( $parameter->children() as $paramValue ) // skip parameter value type
20459       $value[] = reset( $paramValue );
20460     if( 2 > count( $value ))
20461       $params[$key] = html_entity_decode( reset( $value ));
20462     else
20463       $params[$key] = $value;
20464   }
20465   return $params;
20466 }
20467 /**
20468  * parse SimpleXMLElement instance components, create iCalcreator component and update
20469  *
20470  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20471  * @since  2.11.2 - 2012-01-15
20472  * @param  array  $iCal iCalcreator calendar instance
20473  * @param  object $component SimpleXMLElement
20474  * @return void
20475  */
20476 function _getXMLComponents( & $iCal, & $component ) {
20477   $compName = $component->getName();
20478   $comp     = & $iCal->newComponent( $compName );
20479   $subComponents = array( 'valarm', 'standard', 'daylight' );
20480   foreach( $component->children() as $compPart ) { // properties and (opt) subComponents
20481     if( 1 > $compPart->count())
20482       continue;
20483     if( in_array( $compPart->getName(), $subComponents ))
20484       _getXMLComponents( $comp, $compPart );
20485     elseif( 'properties' == $compPart->getName()) {
20486       foreach( $compPart->children() as $property ) // properties as single property
20487         _getXMLProperties( $comp, $property );
20488     }
20489   } // end foreach( $component->children() as $compPart )
20490 }
20491 /**
20492  * parse SimpleXMLElement instance property, create iCalcreator component property
20493  *
20494  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20495  * @since  2.11.2 - 2012-01-27
20496  * @param  array  $iCal iCalcreator calendar instance
20497  * @param  object $component SimpleXMLElement
20498  * @return void
20499  */
20500 function _getXMLProperties( & $iCal, & $property ) {
20501   $propName  = $property->getName();
20502   $value     = $params = array();
20503   $valueType = '';
20504   foreach( $property->children() as $propPart ) { // calendar property parameters (opt) and value(-s)
20505     $valueType = $propPart->getName();
20506     if( 'parameters' == $valueType) {
20507       $params = _getXMLParams( $propPart );
20508       continue;
20509     }
20510     switch( $valueType ) {
20511       case 'binary':
20512         $value = reset( $propPart );
20513         break;
20514       case 'boolean':
20515         break;
20516       case 'cal-address':
20517         $value = reset( $propPart );
20518         break;
20519       case 'date':
20520         $params['VALUE'] = 'DATE';
20521       case 'date-time':
20522         if(( 'exdate' == $propName ) || ( 'rdate' == $propName ))
20523           $value[] = reset( $propPart );
20524         else
20525           $value = reset( $propPart );
20526         break;
20527       case 'duration':
20528         $value = reset( $propPart );
20529         break;
20530 //        case 'geo':
20531       case 'latitude':
20532       case 'longitude':
20533         $value[$valueType] = reset( $propPart );
20534         break;
20535       case 'integer':
20536         $value = reset( $propPart );
20537         break;
20538       case 'period':
20539         if( 'rdate' == $propName )
20540           $params['VALUE'] = 'PERIOD';
20541         $pData = array();
20542         foreach( $propPart->children() as $periodPart )
20543           $pData[] = reset( $periodPart );
20544         if( !empty( $pData ))
20545           $value[] = $pData;
20546         break;
20547 //        case 'rrule':
20548       case 'freq':
20549       case 'count':
20550       case 'until':
20551       case 'interval':
20552       case 'wkst':
20553         $value[$valueType] = reset( $propPart );
20554         break;
20555       case 'bysecond':
20556       case 'byminute':
20557       case 'byhour':
20558       case 'bymonthday':
20559       case 'byyearday':
20560       case 'byweekno':
20561       case 'bymonth':
20562       case 'bysetpos':
20563         $value[$valueType][] = reset( $propPart );
20564         break;
20565       case 'byday':
20566         $byday = reset( $propPart );
20567         if( 2 == strlen( $byday ))
20568           $value[$valueType][] = array( 'DAY' => $byday );
20569         else {
20570           $day = substr( $byday, -2 );
20571           $key = substr( $byday, 0, ( strlen( $byday ) - 2 ));
20572           $value[$valueType][] = array( $key, 'DAY' => $day );
20573         }
20574         break;
20575 //      case 'rstatus':
20576       case 'code':
20577         $value[0] = reset( $propPart );
20578         break;
20579       case 'description':
20580         $value[1] = reset( $propPart );
20581         break;
20582       case 'data':
20583         $value[2] = reset( $propPart );
20584         break;
20585       case 'text':
20586         $text = str_replace( array( "\r\n", "\n\r", "\r", "\n"), '\n', reset( $propPart ));
20587         $value['text'][] = html_entity_decode( $text );
20588         break;
20589       case 'time':
20590         break;
20591       case 'uri':
20592         $value = reset( $propPart );
20593         break;
20594       case 'utc-offset':
20595         $value = str_replace( ':', '', reset( $propPart ));
20596         break;
20597       case 'unknown':
20598       default:
20599         $value = html_entity_decode( reset( $propPart ));
20600         break;
20601     } // end switch( $valueType )
20602   } // end  foreach( $property->children() as $propPart )
20603   if( 'freebusy' == $propName ) {
20604     $fbtype = $params['FBTYPE'];
20605     unset( $params['FBTYPE'] );
20606     $iCal->setProperty( $propName, $fbtype, $value, $params );
20607   }
20608   elseif( 'geo' == $propName )
20609     $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
20610   elseif( 'request-status' == $propName ) {
20611     if( !isset( $value[2] ))
20612       $value[2] = FALSE;
20613     $iCal->setProperty( $propName, $value[0], $value[1], $value[2], $params );
20614   }
20615   else {
20616     if( isset( $value['text'] ) && is_array( $value['text'] )) {
20617       if(( 'categories' == $propName ) || ( 'resources' == $propName ))
20618         $value = $value['text'];
20619       else
20620         $value = reset( $value['text'] );
20621     }
20622     $iCal->setProperty( $propName, $value, $params );
20623   }
20624 }
20625 /*********************************************************************************/
20626 /*          Additional functions to use with vtimezone components                */
20627 /*********************************************************************************/
20628 /**
20629  * For use with
20630  * iCalcreator (kigkonsult.se/iCalcreator/index.php)
20631  * copyright (c) 2011 Yitzchok Lavi
20632  * icalcreator@onebigsystem.com
20633  *
20634  * This library is free software; you can redistribute it and/or
20635  * modify it under the terms of the GNU Lesser General Public
20636  * License as published by the Free Software Foundation; either
20637  * version 2.1 of the License, or (at your option) any later version.
20638  *
20639  * This library is distributed in the hope that it will be useful,
20640  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20641  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20642  * Lesser General Public License for more details.
20643  *
20644  * You should have received a copy of the GNU Lesser General Public
20645  * License along with this library; if not, write to the Free Software
20646  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20647  */
20648 /**
20649  * Additional functions to use with vtimezone components
20650  *
20651  * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')!
20652  *
20653  * @author Yitzchok Lavi <icalcreator@onebigsystem.com>
20654  *         adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
20655  * @version 1.0.2 - 2011-02-24
20656  *
20657  */
20658 /**
20659  * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the
20660  * timezone, according to the VTIMEZONE information in the input array.
20661  *
20662  * $param array  $timezonesarray, output from function getTimezonesAsDateArrays (below)
20663  * $param string $tzid,           time zone identifier
20664  * $param mixed  $timestamp,      timestamp or a UTC datetime (in array format)
20665  * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname'
20666  *
20667  */
20668 function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) {
20669     if( is_array( $timestamp )) {
20670 //$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] ); // test ###
20671       $timestamp = gmmktime(
+20672             $timestamp['hour'],
+20673             $timestamp['min'],
+20674             $timestamp['sec'],
+20675             $timestamp['month'],
+20676             $timestamp['day'],
+20677             $timestamp['year']
+20678             ) ;
20679 // echo '<td colspan="4">&nbsp;'."\n".'<tr><td>&nbsp;<td class="r">'.$timestamp.'<td class="r">'.$disp.'<td colspan="4">&nbsp;'."\n".'<tr><td colspan="3">&nbsp;'; // test ###
20680     }
20681     $tzoffset = array();
20682     // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates)
20683     $tzoffset['offsetHis'] = '+0000';
20684     $tzoffset['offsetSec'] = 0;
20685     $tzoffset['tzname']    = '?';
20686     if( !isset( $timezonesarray[$tzid] ))
20687       return $tzoffset;
20688     $tzdatearray = $timezonesarray[$tzid];
20689     if ( is_array($tzdatearray) ) {
20690         sort($tzdatearray); // just in case
20691         if ( $timestamp < $tzdatearray[0]['timestamp'] ) {
20692             // our date is before the first change
20693             $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ;
20694             $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ;
20695             $tzoffset['tzname']    = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case
20696         } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) {
20697             // our date is after the last change (we do this so our scan can stop at the last record but one)
20698             $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ;
20699             $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ;
20700             $tzoffset['tzname']    = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ;
20701         } else {
20702             // our date somewhere in between
20703             // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it
20704             // we don't include the last date in our loop as there isn't one after it to check
20705             for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) {
20706                 if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) {
20707                     $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ;
20708                     $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ;
20709                     $tzoffset['tzname']    = $tzdatearray[$i]['tzafter']['tzname'] ;
20710                     break;
20711                 }
20712             }
20713         }
20714     }
20715     return $tzoffset;
20716 }
20717 /**
20718  * Returns an array containing all the timezone data in the vcalendar object
20719  *
20720  * @param object $vcalendar, iCalcreator calendar instance
20721  * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
20722  *                based on the timezone data in the vcalendar object
20723  *
20724  */
20725 function getTimezonesAsDateArrays($vcalendar) {
20726     $timezonedata = array();
20727     while( $vtz = $vcalendar->getComponent( 'vtimezone' )) {
20728         $tzid       = $vtz->getProperty('tzid');
20729         $alltzdates = array();
20730         while ( $vtzc = $vtz->getComponent( 'standard' )) {
20731             $newtzdates = expandTimezoneDates($vtzc);
20732             $alltzdates = array_merge($alltzdates, $newtzdates);
20733         }
20734         while ( $vtzc = $vtz->getComponent( 'daylight' )) {
20735             $newtzdates = expandTimezoneDates($vtzc);
20736             $alltzdates = array_merge($alltzdates, $newtzdates);
20737         }
20738         sort($alltzdates);
20739         $timezonedata[$tzid] = $alltzdates;
20740     }
20741     return $timezonedata;
20742 }
20743 /**
20744  * Returns an array containing time zone data from vtimezone standard/daylight instances
20745  *
20746  * @param object $vtzc, an iCalcreator calendar standard/daylight instance
20747  * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
20748  *
20749  */
20750 function expandTimezoneDates($vtzc) {
20751     $tzdates = array();
20752     // prepare time zone "description" to attach to each change
20753     $tzbefore = array();
20754     $tzbefore['offsetHis']  = $vtzc->getProperty('tzoffsetfrom') ;
20755     $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']);
20756     if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 )))
20757       $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec'];
20758     $tzafter = array();
20759     $tzafter['offsetHis']   = $vtzc->getProperty('tzoffsetto') ;
20760     $tzafter['offsetSec']  = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']);
20761     if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 )))
20762       $tzafter['offsetSec'] = '+'.$tzafter['offsetSec'];
20763     if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname')))
20764       $tzafter['tzname'] = $tzafter['offsetHis'];
20765     // find out where to start from
20766     $dtstart = $vtzc->getProperty('dtstart');
20767     $dtstarttimestamp = mktime(
+20768             $dtstart['hour'],
+20769             $dtstart['min'],
+20770             $dtstart['sec'],
+20771             $dtstart['month'],
+20772             $dtstart['day'],
+20773             $dtstart['year']
+20774             ) ;
20775     if( !isset( $dtstart['unparsedtext'] )) // ??
20776       $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] );
20777     if ( $dtstarttimestamp == 0 ) {
20778         // it seems that the dtstart string may not have parsed correctly
20779         // let's set a timestamp starting from 1902, using the time part of the original string
20780         // so that the time will change at the right time of day
20781         // at worst we'll get midnight again
20782         $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ;
20783         $dtstarttimestamp = strtotime("19020101",0);
20784         $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp);
20785     }
20786     // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp
20787     $diff  = -1 * $tzbefore['offsetSec'];
20788     $dtstarttimestamp += $diff;
20789                 // add this (start) change to the array of changes
20790     $tzdates[] = array(
+20791         'timestamp' => $dtstarttimestamp,
+20792         'tzbefore'  => $tzbefore,
+20793         'tzafter'   => $tzafter
+20794         );
20795     $datearray = getdate($dtstarttimestamp);
20796     // save original array to use time parts, because strtotime (used below) apparently loses the time
20797     $changetime = $datearray ;
20798     // generate dates according to an RRULE line
20799     $rrule = $vtzc->getProperty('rrule') ;
20800     if ( is_array($rrule) ) {
20801         if ( $rrule['FREQ'] == 'YEARLY' ) {
20802             // calculate transition dates starting from DTSTART
20803             $offsetchangetimestamp = $dtstarttimestamp;
20804             // calculate transition dates until 10 years in the future
20805             $stoptimestamp = strtotime("+10 year",time());
20806             // if UNTIL is set, calculate until then (however far ahead)
20807             if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) {
20808                 $stoptimestamp = mktime(
+20809                     $rrule['UNTIL']['hour'],
+20810                     $rrule['UNTIL']['min'],
+20811                     $rrule['UNTIL']['sec'],
+20812                     $rrule['UNTIL']['month'],
+20813                     $rrule['UNTIL']['day'],
+20814                     $rrule['UNTIL']['year']
+20815                     ) ;
20816             }
20817             $count = 0 ;
20818             $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ;
20819             $daynames = array(
+20820                         'SU' => 'Sunday',
+20821                         'MO' => 'Monday',
+20822                         'TU' => 'Tuesday',
+20823                         'WE' => 'Wednesday',
+20824                         'TH' => 'Thursday',
+20825                         'FR' => 'Friday',
+20826                         'SA' => 'Saturday'
+20827                         );
20828             // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates
20829             while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) {
20830                 // break up the timestamp into its parts
20831                 $datearray = getdate($offsetchangetimestamp);
20832                 if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) {
20833                     // set the month
20834                     $datearray['mon'] = $rrule['BYMONTH'] ;
20835                 }
20836                 if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) {
20837                     // set specific day of month
20838                     $datearray['mday']  = $rrule['BYMONTHDAY'];
20839                 } elseif ( is_array($rrule['BYDAY']) ) {
20840                     // find the Xth WKDAY in the month
20841                     // the starting point for this process is the first of the month set above
20842                     $datearray['mday'] = 1 ;
20843                     // turn $datearray as it is now back into a timestamp
20844                     $offsetchangetimestamp = mktime(
+20845                         $datearray['hours'],
+20846                         $datearray['minutes'],
+20847                         $datearray['seconds'],
+20848                         $datearray['mon'],
+20849                         $datearray['mday'],
+20850                         $datearray['year']
+20851                             );
20852                     if ($rrule['BYDAY'][0] > 0) {
20853                         // to find Xth WKDAY in month, we find last WKDAY in month before
20854                         // we do that by finding first WKDAY in this month and going back one week
20855                         // then we add X weeks (below)
20856                         $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
20857                         $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp);
20858                     } else {
20859                         // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month
20860                         // we do that by going forward one month and going to WKDAY there
20861                         // then we subtract X weeks (below)
20862                         $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp);
20863                         $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
20864                     }
20865                     // now move forward or back the appropriate number of weeks, into the month we want
20866                     $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp);
20867                     $datearray = getdate($offsetchangetimestamp);
20868                 }
20869                 // convert the date parts back into a timestamp, setting the time parts according to the
20870                 // original time data which we stored
20871                 $offsetchangetimestamp = mktime(
+20872                     $changetime['hours'],
+20873                     $changetime['minutes'],
+20874                     $changetime['seconds'] + $diff,
+20875                     $datearray['mon'],
+20876                     $datearray['mday'],
+20877                     $datearray['year']
+20878                         );
20879                 // add this change to the array of changes
20880                 $tzdates[] = array(
+20881                     'timestamp' => $offsetchangetimestamp,
+20882                     'tzbefore'  => $tzbefore,
+20883                     'tzafter'   => $tzafter
+20884                     );
20885                 // update counters (timestamp and count)
20886                 $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp);
20887                 $count += 1 ;
20888             }
20889         }
20890     }
20891     // generate dates according to RDATE lines
20892     while ($rdates = $vtzc->getProperty('rdate')) {
20893         if ( is_array($rdates) ) {
20894 
20895             foreach ( $rdates as $rdate ) {
20896                 // convert the explicit change date to a timestamp
20897                 $offsetchangetimestamp = mktime(
+20898                         $rdate['hour'],
+20899                         $rdate['min'],
+20900                         $rdate['sec'] + $diff,
+20901                         $rdate['month'],
+20902                         $rdate['day'],
+20903                         $rdate['year']
+20904                         ) ;
20905                 // add this change to the array of changes
20906                 $tzdates[] = array(
+20907                     'timestamp' => $offsetchangetimestamp,
+20908                     'tzbefore'  => $tzbefore,
+20909                     'tzafter'   => $tzafter
+20910                     );
20911             }
20912         }
20913     }
20914     return $tzdates;
20915 }
20916 ?>